| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_basegfx.hxx" |
| #include <basegfx/numeric/ftools.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <osl/diagnose.h> |
| #include <rtl/math.hxx> |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolypolygon.hxx> |
| #include <basegfx/range/b2drange.hxx> |
| #include <basegfx/curve/b2dcubicbezier.hxx> |
| #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
| #include <basegfx/point/b3dpoint.hxx> |
| #include <basegfx/matrix/b3dhommatrix.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/curve/b2dbeziertools.hxx> |
| #include <basegfx/matrix/b2dhommatrixtools.hxx> |
| #include <osl/mutex.hxx> |
| |
| #include <numeric> |
| #include <limits> |
| |
| // #i37443# |
| #define ANGLE_BOUND_START_VALUE (2.25) |
| #define ANGLE_BOUND_MINIMUM_VALUE (0.1) |
| #define COUNT_SUBDIVIDE_DEFAULT (4L) |
| #ifdef DBG_UTIL |
| static double fAngleBoundStartValue = ANGLE_BOUND_START_VALUE; |
| #endif |
| #define STEPSPERQUARTER (3) |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace tools |
| { |
| void openWithGeometryChange(B2DPolygon& rCandidate) |
| { |
| if(rCandidate.isClosed()) |
| { |
| if(rCandidate.count()) |
| { |
| rCandidate.append(rCandidate.getB2DPoint(0)); |
| |
| if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0)) |
| { |
| rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0)); |
| rCandidate.resetPrevControlPoint(0); |
| } |
| } |
| |
| rCandidate.setClosed(false); |
| } |
| } |
| |
| void closeWithGeometryChange(B2DPolygon& rCandidate) |
| { |
| if(!rCandidate.isClosed()) |
| { |
| while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1)) |
| { |
| if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1)) |
| { |
| rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1)); |
| } |
| |
| rCandidate.remove(rCandidate.count() - 1); |
| } |
| |
| rCandidate.setClosed(true); |
| } |
| } |
| |
| void checkClosed(B2DPolygon& rCandidate) |
| { |
| // #i80172# Removed unnecessary assertion |
| // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)"); |
| |
| if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1)) |
| { |
| closeWithGeometryChange(rCandidate); |
| } |
| } |
| |
| // Get successor and predecessor indices. Returning the same index means there |
| // is none. Same for successor. |
| sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate) |
| { |
| OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)"); |
| |
| if(nIndex) |
| { |
| return nIndex - 1L; |
| } |
| else if(rCandidate.count()) |
| { |
| return rCandidate.count() - 1L; |
| } |
| else |
| { |
| return nIndex; |
| } |
| } |
| |
| sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate) |
| { |
| OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)"); |
| |
| if(nIndex + 1L < rCandidate.count()) |
| { |
| return nIndex + 1L; |
| } |
| else if(nIndex + 1L == rCandidate.count()) |
| { |
| return 0L; |
| } |
| else |
| { |
| return nIndex; |
| } |
| } |
| |
| B2VectorOrientation getOrientation(const B2DPolygon& rCandidate) |
| { |
| B2VectorOrientation eRetval(ORIENTATION_NEUTRAL); |
| |
| if(rCandidate.count() > 2L || rCandidate.areControlPointsUsed()) |
| { |
| const double fSignedArea(getSignedArea(rCandidate)); |
| |
| if(fTools::equalZero(fSignedArea)) |
| { |
| // ORIENTATION_NEUTRAL, already set |
| } |
| if(fSignedArea > 0.0) |
| { |
| eRetval = ORIENTATION_POSITIVE; |
| } |
| else if(fSignedArea < 0.0) |
| { |
| eRetval = ORIENTATION_NEGATIVE; |
| } |
| } |
| |
| return eRetval; |
| } |
| |
| B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| return rCandidate.getContinuityInPoint(nIndex); |
| } |
| |
| B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DPolygon aRetval; |
| |
| if(nPointCount) |
| { |
| // prepare edge-oriented loop |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DCubicBezier aBezier; |
| aBezier.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| // perf: try to avoid too many realloctions by guessing the result's pointcount |
| aRetval.reserve(nPointCount*4); |
| |
| // add start point (always) |
| aRetval.append(aBezier.getStartPoint()); |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get next and control points |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aBezier.testAndSolveTrivialBezier(); |
| |
| if(aBezier.isBezier()) |
| { |
| // add curved edge and generate DistanceBound |
| double fBound(0.0); |
| |
| if(0.0 == fDistanceBound) |
| { |
| // If not set, use B2DCubicBezier functionality to guess a rough value |
| const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0); |
| |
| // take 1/100th of the rough curve length |
| fBound = fRoughLength * 0.01; |
| } |
| else |
| { |
| // use given bound value |
| fBound = fDistanceBound; |
| } |
| |
| // make sure bound value is not too small. The base units are 1/100th mm, thus |
| // just make sure it's not smaller then 1/100th of that |
| if(fBound < 0.01) |
| { |
| fBound = 0.01; |
| } |
| |
| // call adaptive subdivide which adds edges to aRetval accordingly |
| aBezier.adaptiveSubdivideByDistance(aRetval, fBound); |
| } |
| else |
| { |
| // add non-curved edge |
| aRetval.append(aBezier.getEndPoint()); |
| } |
| |
| // prepare next step |
| aBezier.setStartPoint(aBezier.getEndPoint()); |
| } |
| |
| if(rCandidate.isClosed()) |
| { |
| // set closed flag and correct last point (which is added double now). |
| closeWithGeometryChange(aRetval); |
| } |
| } |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DPolygon aRetval; |
| |
| if(nPointCount) |
| { |
| // prepare edge-oriented loop |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DCubicBezier aBezier; |
| aBezier.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| // perf: try to avoid too many realloctions by guessing the result's pointcount |
| aRetval.reserve(nPointCount*4); |
| |
| // add start point (always) |
| aRetval.append(aBezier.getStartPoint()); |
| |
| // #i37443# prepare convenient AngleBound if none was given |
| if(0.0 == fAngleBound) |
| { |
| #ifdef DBG_UTIL |
| fAngleBound = fAngleBoundStartValue; |
| #else |
| fAngleBound = ANGLE_BOUND_START_VALUE; |
| #endif |
| } |
| else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE)) |
| { |
| fAngleBound = 0.1; |
| } |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get next and control points |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aBezier.testAndSolveTrivialBezier(); |
| |
| if(aBezier.isBezier()) |
| { |
| // call adaptive subdivide |
| aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound, true); |
| } |
| else |
| { |
| // add non-curved edge |
| aRetval.append(aBezier.getEndPoint()); |
| } |
| |
| // prepare next step |
| aBezier.setStartPoint(aBezier.getEndPoint()); |
| } |
| |
| if(rCandidate.isClosed()) |
| { |
| // set closed flag and correct last point (which is added double now). |
| closeWithGeometryChange(aRetval); |
| } |
| } |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| B2DPolygon adaptiveSubdivideByCount(const B2DPolygon& rCandidate, sal_uInt32 nCount) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DPolygon aRetval; |
| |
| if(nPointCount) |
| { |
| // prepare edge-oriented loop |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DCubicBezier aBezier; |
| aBezier.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| // perf: try to avoid too many realloctions by guessing the result's pointcount |
| aRetval.reserve(nPointCount*4); |
| |
| // add start point (always) |
| aRetval.append(aBezier.getStartPoint()); |
| |
| // #i37443# prepare convenient count if none was given |
| if(0L == nCount) |
| { |
| nCount = COUNT_SUBDIVIDE_DEFAULT; |
| } |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get next and control points |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aBezier.testAndSolveTrivialBezier(); |
| |
| if(aBezier.isBezier()) |
| { |
| // call adaptive subdivide |
| aBezier.adaptiveSubdivideByCount(aRetval, nCount); |
| } |
| else |
| { |
| // add non-curved edge |
| aRetval.append(aBezier.getEndPoint()); |
| } |
| |
| // prepare next step |
| aBezier.setStartPoint(aBezier.getEndPoint()); |
| } |
| |
| if(rCandidate.isClosed()) |
| { |
| // set closed flag and correct last point (which is added double now). |
| closeWithGeometryChange(aRetval); |
| } |
| } |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder) |
| { |
| const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); |
| |
| if(bWithBorder && isPointOnPolygon(aCandidate, rPoint, true)) |
| { |
| return true; |
| } |
| else |
| { |
| bool bRetval(false); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| |
| if(nPointCount) |
| { |
| B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1L)); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aPreviousPoint(aCurrentPoint); |
| aCurrentPoint = aCandidate.getB2DPoint(a); |
| |
| // cross-over in Y? |
| const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY())); |
| const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY())); |
| |
| if(bCompYA != bCompYB) |
| { |
| // cross-over in X? |
| const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX())); |
| const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX())); |
| |
| if(bCompXA == bCompXB) |
| { |
| if(bCompXA) |
| { |
| bRetval = !bRetval; |
| } |
| } |
| else |
| { |
| const double fCompare( |
| aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) * |
| (aPreviousPoint.getX() - aCurrentPoint.getX()) / |
| (aPreviousPoint.getY() - aCurrentPoint.getY())); |
| |
| if(fTools::more(fCompare, rPoint.getX())) |
| { |
| bRetval = !bRetval; |
| } |
| } |
| } |
| } |
| } |
| |
| return bRetval; |
| } |
| } |
| |
| bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder) |
| { |
| const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); |
| const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon); |
| const sal_uInt32 nPointCount(aPolygon.count()); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aTestPoint(aPolygon.getB2DPoint(a)); |
| |
| if(!isInside(aCandidate, aTestPoint, bWithBorder)) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| B2DRange getRangeWithControlPoints(const B2DPolygon& rCandidate) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DRange aRetval; |
| |
| if(nPointCount) |
| { |
| const bool bControlPointsUsed(rCandidate.areControlPointsUsed()); |
| |
| for(sal_uInt32 a(0); a < nPointCount; a++) |
| { |
| aRetval.expand(rCandidate.getB2DPoint(a)); |
| |
| if(bControlPointsUsed) |
| { |
| aRetval.expand(rCandidate.getNextControlPoint(a)); |
| aRetval.expand(rCandidate.getPrevControlPoint(a)); |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DRange getRange(const B2DPolygon& rCandidate) |
| { |
| // changed to use internally buffered version at B2DPolygon |
| return rCandidate.getB2DRange(); |
| } |
| |
| double getSignedArea(const B2DPolygon& rCandidate) |
| { |
| const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); |
| double fRetval(0.0); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| |
| if(nPointCount > 2) |
| { |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1L : a - 1L)); |
| const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a)); |
| |
| fRetval += aPreviousPoint.getX() * aCurrentPoint.getY(); |
| fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX(); |
| } |
| |
| // correct to zero if small enough. Also test the quadratic |
| // of the result since the precision is near quadratic due to |
| // the algorithm |
| if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval)) |
| { |
| fRetval = 0.0; |
| } |
| } |
| |
| return fRetval; |
| } |
| |
| double getArea(const B2DPolygon& rCandidate) |
| { |
| double fRetval(0.0); |
| |
| if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed()) |
| { |
| fRetval = getSignedArea(rCandidate); |
| const double fZero(0.0); |
| |
| if(fTools::less(fRetval, fZero)) |
| { |
| fRetval = -fRetval; |
| } |
| } |
| |
| return fRetval; |
| } |
| |
| double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)"); |
| double fRetval(0.0); |
| |
| if(nPointCount) |
| { |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| B2DCubicBezier aEdge; |
| |
| aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex)); |
| aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex)); |
| aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| |
| fRetval = aEdge.getLength(); |
| } |
| else |
| { |
| const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex)); |
| const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex)); |
| |
| fRetval = B2DVector(aNext - aCurrent).getLength(); |
| } |
| } |
| |
| return fRetval; |
| } |
| |
| double getLength(const B2DPolygon& rCandidate) |
| { |
| double fRetval(0.0); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount) |
| { |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| B2DCubicBezier aEdge; |
| aEdge.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aEdge.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| |
| fRetval += aEdge.getLength(); |
| aEdge.setStartPoint(aEdge.getEndPoint()); |
| } |
| } |
| else |
| { |
| B2DPoint aCurrent(rCandidate.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex)); |
| |
| fRetval += B2DVector(aNext - aCurrent).getLength(); |
| aCurrent = aNext; |
| } |
| } |
| } |
| |
| return fRetval; |
| } |
| |
| B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength) |
| { |
| B2DPoint aRetval; |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if( 1L == nPointCount ) |
| { |
| // only one point (i.e. no edge) - simply take that point |
| aRetval = rCandidate.getB2DPoint(0); |
| } |
| else if(nPointCount > 1L) |
| { |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| sal_uInt32 nIndex(0L); |
| bool bIndexDone(false); |
| |
| // get length if not given |
| if(fTools::equalZero(fLength)) |
| { |
| fLength = getLength(rCandidate); |
| } |
| |
| if(fTools::less(fDistance, 0.0)) |
| { |
| // handle fDistance < 0.0 |
| if(rCandidate.isClosed()) |
| { |
| // if fDistance < 0.0 increment with multiple of fLength |
| sal_uInt32 nCount(sal_uInt32(-fDistance / fLength)); |
| fDistance += double(nCount + 1L) * fLength; |
| } |
| else |
| { |
| // crop to polygon start |
| fDistance = 0.0; |
| bIndexDone = true; |
| } |
| } |
| else if(fTools::moreOrEqual(fDistance, fLength)) |
| { |
| // handle fDistance >= fLength |
| if(rCandidate.isClosed()) |
| { |
| // if fDistance >= fLength decrement with multiple of fLength |
| sal_uInt32 nCount(sal_uInt32(fDistance / fLength)); |
| fDistance -= (double)(nCount) * fLength; |
| } |
| else |
| { |
| // crop to polygon end |
| fDistance = 0.0; |
| nIndex = nEdgeCount; |
| bIndexDone = true; |
| } |
| } |
| |
| // look for correct index. fDistance is now [0.0 .. fLength[ |
| double fEdgeLength(getEdgeLength(rCandidate, nIndex)); |
| |
| while(!bIndexDone) |
| { |
| // edge found must be on the half-open range |
| // [0,fEdgeLength). |
| // Note that in theory, we cannot move beyond |
| // the last polygon point, since fDistance>=fLength |
| // is checked above. Unfortunately, with floating- |
| // point calculations, this case might happen. |
| // Handled by nIndex check below |
| if(nIndex < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength)) |
| { |
| // go to next edge |
| fDistance -= fEdgeLength; |
| fEdgeLength = getEdgeLength(rCandidate, ++nIndex); |
| } |
| else |
| { |
| // it's on this edge, stop |
| bIndexDone = true; |
| } |
| } |
| |
| // get the point using nIndex |
| aRetval = rCandidate.getB2DPoint(nIndex); |
| |
| // if fDistance != 0.0, move that length on the edge. The edge |
| // length is in fEdgeLength. |
| if(!fTools::equalZero(fDistance)) |
| { |
| if(fTools::moreOrEqual(fDistance, fEdgeLength)) |
| { |
| // end point of choosen edge |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| aRetval = rCandidate.getB2DPoint(nNextIndex); |
| } |
| else if(fTools::equalZero(fDistance)) |
| { |
| // start point of choosen edge |
| aRetval = aRetval; |
| } |
| else |
| { |
| // inside edge |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); |
| bool bDone(false); |
| |
| // add calculated average value to the return value |
| if(rCandidate.areControlPointsUsed()) |
| { |
| // get as bezier segment |
| const B2DCubicBezier aBezierSegment( |
| aRetval, rCandidate.getNextControlPoint(nIndex), |
| rCandidate.getPrevControlPoint(nNextIndex), aNextPoint); |
| |
| if(aBezierSegment.isBezier()) |
| { |
| // use B2DCubicBezierHelper to bridge the non-linear gap between |
| // length and bezier distances |
| const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); |
| const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance)); |
| |
| aRetval = aBezierSegment.interpolatePoint(fBezierDistance); |
| bDone = true; |
| } |
| } |
| |
| if(!bDone) |
| { |
| const double fRelativeInEdge(fDistance / fEdgeLength); |
| aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge); |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength) |
| { |
| // get length if not given |
| if(fTools::equalZero(fLength)) |
| { |
| fLength = getLength(rCandidate); |
| } |
| |
| // multiply fDistance with real length to get absolute position and |
| // use getPositionAbsolute |
| return getPositionAbsolute(rCandidate, fDistance * fLength, fLength); |
| } |
| |
| B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount) |
| { |
| // get length if not given |
| if(fTools::equalZero(fLength)) |
| { |
| fLength = getLength(rCandidate); |
| } |
| |
| // test and correct fFrom |
| if(fTools::less(fFrom, 0.0)) |
| { |
| fFrom = 0.0; |
| } |
| |
| // test and correct fTo |
| if(fTools::more(fTo, fLength)) |
| { |
| fTo = fLength; |
| } |
| |
| // test and correct relationship of fFrom, fTo |
| if(fTools::more(fFrom, fTo)) |
| { |
| fFrom = fTo = (fFrom + fTo) / 2.0; |
| } |
| |
| if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength)) |
| { |
| // no change, result is the whole polygon |
| return rCandidate; |
| } |
| else |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| double fPositionOfStart(0.0); |
| bool bStartDone(false); |
| bool bEndDone(false); |
| |
| for(sal_uInt32 a(0L); !(bStartDone && bEndDone) && a < nEdgeCount; a++) |
| { |
| const double fEdgeLength(getEdgeLength(rCandidate, a)); |
| |
| if(!bStartDone) |
| { |
| if(fTools::equalZero(fFrom)) |
| { |
| aRetval.append(rCandidate.getB2DPoint(a)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a)); |
| } |
| |
| bStartDone = true; |
| } |
| else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength)) |
| { |
| // calculate and add start point |
| if(fTools::equalZero(fEdgeLength)) |
| { |
| aRetval.append(rCandidate.getB2DPoint(a)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a)); |
| } |
| } |
| else |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aStart(rCandidate.getB2DPoint(a)); |
| const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); |
| bool bDone(false); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| const B2DCubicBezier aBezierSegment( |
| aStart, rCandidate.getNextControlPoint(a), |
| rCandidate.getPrevControlPoint(nNextIndex), aEnd); |
| |
| if(aBezierSegment.isBezier()) |
| { |
| // use B2DCubicBezierHelper to bridge the non-linear gap between |
| // length and bezier distances |
| const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); |
| const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart)); |
| B2DCubicBezier aRight; |
| |
| aBezierSegment.split(fBezierDistance, 0, &aRight); |
| aRetval.append(aRight.getStartPoint()); |
| aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA()); |
| bDone = true; |
| } |
| } |
| |
| if(!bDone) |
| { |
| const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength); |
| aRetval.append(interpolate(aStart, aEnd, fRelValue)); |
| } |
| } |
| |
| bStartDone = true; |
| |
| // if same point, end is done, too. |
| if(fFrom == fTo) |
| { |
| bEndDone = true; |
| } |
| } |
| } |
| |
| if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength)) |
| { |
| // calculate and add end point |
| if(fTools::equalZero(fEdgeLength)) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aRetval.append(rCandidate.getB2DPoint(nNextIndex)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex)); |
| } |
| } |
| else |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aStart(rCandidate.getB2DPoint(a)); |
| const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); |
| bool bDone(false); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| const B2DCubicBezier aBezierSegment( |
| aStart, rCandidate.getNextControlPoint(a), |
| rCandidate.getPrevControlPoint(nNextIndex), aEnd); |
| |
| if(aBezierSegment.isBezier()) |
| { |
| // use B2DCubicBezierHelper to bridge the non-linear gap between |
| // length and bezier distances |
| const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); |
| const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart)); |
| B2DCubicBezier aLeft; |
| |
| aBezierSegment.split(fBezierDistance, &aLeft, 0); |
| aRetval.append(aLeft.getEndPoint()); |
| aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB()); |
| bDone = true; |
| } |
| } |
| |
| if(!bDone) |
| { |
| const double fRelValue((fTo - fPositionOfStart) / fEdgeLength); |
| aRetval.append(interpolate(aStart, aEnd, fRelValue)); |
| } |
| } |
| |
| bEndDone = true; |
| } |
| |
| if(!bEndDone) |
| { |
| if(bStartDone) |
| { |
| // add segments end point |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aRetval.append(rCandidate.getB2DPoint(nNextIndex)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex)); |
| aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex)); |
| } |
| } |
| |
| // increment fPositionOfStart |
| fPositionOfStart += fEdgeLength; |
| } |
| } |
| return aRetval; |
| } |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| B2DPolygon getSnippetRelative(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength) |
| { |
| // get length if not given |
| if(fTools::equalZero(fLength)) |
| { |
| fLength = getLength(rCandidate); |
| } |
| |
| // multiply distances with real length to get absolute position and |
| // use getSnippetAbsolute |
| return getSnippetAbsolute(rCandidate, fFrom * fLength, fTo * fLength, fLength); |
| } |
| |
| CutFlagValue findCut( |
| const B2DPolygon& rCandidate, |
| sal_uInt32 nIndex1, sal_uInt32 nIndex2, |
| CutFlagValue aCutFlags, |
| double* pCut1, double* pCut2) |
| { |
| CutFlagValue aRetval(CUTFLAG_NONE); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nIndex1 < nPointCount && nIndex2 < nPointCount && nIndex1 != nIndex2) |
| { |
| sal_uInt32 nEnd1(getIndexOfSuccessor(nIndex1, rCandidate)); |
| sal_uInt32 nEnd2(getIndexOfSuccessor(nIndex2, rCandidate)); |
| |
| const B2DPoint aStart1(rCandidate.getB2DPoint(nIndex1)); |
| const B2DPoint aEnd1(rCandidate.getB2DPoint(nEnd1)); |
| const B2DVector aVector1(aEnd1 - aStart1); |
| |
| const B2DPoint aStart2(rCandidate.getB2DPoint(nIndex2)); |
| const B2DPoint aEnd2(rCandidate.getB2DPoint(nEnd2)); |
| const B2DVector aVector2(aEnd2 - aStart2); |
| |
| aRetval = findCut( |
| aStart1, aVector1, aStart2, aVector2, |
| aCutFlags, pCut1, pCut2); |
| } |
| |
| return aRetval; |
| } |
| |
| CutFlagValue findCut( |
| const B2DPolygon& rCandidate1, sal_uInt32 nIndex1, |
| const B2DPolygon& rCandidate2, sal_uInt32 nIndex2, |
| CutFlagValue aCutFlags, |
| double* pCut1, double* pCut2) |
| { |
| CutFlagValue aRetval(CUTFLAG_NONE); |
| const sal_uInt32 nPointCount1(rCandidate1.count()); |
| const sal_uInt32 nPointCount2(rCandidate2.count()); |
| |
| if(nIndex1 < nPointCount1 && nIndex2 < nPointCount2) |
| { |
| sal_uInt32 nEnd1(getIndexOfSuccessor(nIndex1, rCandidate1)); |
| sal_uInt32 nEnd2(getIndexOfSuccessor(nIndex2, rCandidate2)); |
| |
| const B2DPoint aStart1(rCandidate1.getB2DPoint(nIndex1)); |
| const B2DPoint aEnd1(rCandidate1.getB2DPoint(nEnd1)); |
| const B2DVector aVector1(aEnd1 - aStart1); |
| |
| const B2DPoint aStart2(rCandidate2.getB2DPoint(nIndex2)); |
| const B2DPoint aEnd2(rCandidate2.getB2DPoint(nEnd2)); |
| const B2DVector aVector2(aEnd2 - aStart2); |
| |
| aRetval = findCut( |
| aStart1, aVector1, aStart2, aVector2, |
| aCutFlags, pCut1, pCut2); |
| } |
| |
| return aRetval; |
| } |
| |
| CutFlagValue findCut( |
| const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta, |
| const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta, |
| CutFlagValue aCutFlags, |
| double* pCut1, double* pCut2) |
| { |
| CutFlagValue aRetval(CUTFLAG_NONE); |
| double fCut1(0.0); |
| double fCut2(0.0); |
| bool bFinished(!((bool)(aCutFlags & CUTFLAG_ALL))); |
| |
| // test for same points? |
| if(!bFinished |
| && (aCutFlags & (CUTFLAG_START1|CUTFLAG_END1)) |
| && (aCutFlags & (CUTFLAG_START2|CUTFLAG_END2))) |
| { |
| // same startpoint? |
| if(!bFinished && (aCutFlags & (CUTFLAG_START1|CUTFLAG_START2)) == (CUTFLAG_START1|CUTFLAG_START2)) |
| { |
| if(rEdge1Start.equal(rEdge2Start)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_START1|CUTFLAG_START2); |
| } |
| } |
| |
| // same endpoint? |
| if(!bFinished && (aCutFlags & (CUTFLAG_END1|CUTFLAG_END2)) == (CUTFLAG_END1|CUTFLAG_END2)) |
| { |
| const B2DPoint aEnd1(rEdge1Start + rEdge1Delta); |
| const B2DPoint aEnd2(rEdge2Start + rEdge2Delta); |
| |
| if(aEnd1.equal(aEnd2)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_END1|CUTFLAG_END2); |
| fCut1 = fCut2 = 1.0; |
| } |
| } |
| |
| // startpoint1 == endpoint2? |
| if(!bFinished && (aCutFlags & (CUTFLAG_START1|CUTFLAG_END2)) == (CUTFLAG_START1|CUTFLAG_END2)) |
| { |
| const B2DPoint aEnd2(rEdge2Start + rEdge2Delta); |
| |
| if(rEdge1Start.equal(aEnd2)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_START1|CUTFLAG_END2); |
| fCut1 = 0.0; |
| fCut2 = 1.0; |
| } |
| } |
| |
| // startpoint2 == endpoint1? |
| if(!bFinished&& (aCutFlags & (CUTFLAG_START2|CUTFLAG_END1)) == (CUTFLAG_START2|CUTFLAG_END1)) |
| { |
| const B2DPoint aEnd1(rEdge1Start + rEdge1Delta); |
| |
| if(rEdge2Start.equal(aEnd1)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_START2|CUTFLAG_END1); |
| fCut1 = 1.0; |
| fCut2 = 0.0; |
| } |
| } |
| } |
| |
| if(!bFinished && (aCutFlags & CUTFLAG_LINE)) |
| { |
| if(!bFinished && (aCutFlags & CUTFLAG_START1)) |
| { |
| // start1 on line 2 ? |
| if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_LINE|CUTFLAG_START1); |
| } |
| } |
| |
| if(!bFinished && (aCutFlags & CUTFLAG_START2)) |
| { |
| // start2 on line 1 ? |
| if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_LINE|CUTFLAG_START2); |
| } |
| } |
| |
| if(!bFinished && (aCutFlags & CUTFLAG_END1)) |
| { |
| // end1 on line 2 ? |
| const B2DPoint aEnd1(rEdge1Start + rEdge1Delta); |
| |
| if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_LINE|CUTFLAG_END1); |
| } |
| } |
| |
| if(!bFinished && (aCutFlags & CUTFLAG_END2)) |
| { |
| // end2 on line 1 ? |
| const B2DPoint aEnd2(rEdge2Start + rEdge2Delta); |
| |
| if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1)) |
| { |
| bFinished = true; |
| aRetval = (CUTFLAG_LINE|CUTFLAG_END2); |
| } |
| } |
| |
| if(!bFinished) |
| { |
| // cut in line1, line2 ? |
| fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX()); |
| |
| if(!fTools::equalZero(fCut1)) |
| { |
| fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX()) |
| + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1; |
| |
| const double fZero(0.0); |
| const double fOne(1.0); |
| |
| // inside parameter range edge1 AND fCut2 is calcable |
| if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne) |
| && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY()))) |
| { |
| // take the mopre precise calculation of the two possible |
| if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY())) |
| { |
| fCut2 = (rEdge1Start.getX() + fCut1 |
| * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX(); |
| } |
| else |
| { |
| fCut2 = (rEdge1Start.getY() + fCut1 |
| * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY(); |
| } |
| |
| // inside parameter range edge2, too |
| if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne)) |
| { |
| bFinished = true; |
| aRetval = CUTFLAG_LINE; |
| } |
| } |
| } |
| } |
| } |
| |
| // copy values if wanted |
| if(pCut1) |
| { |
| *pCut1 = fCut1; |
| } |
| |
| if(pCut2) |
| { |
| *pCut2 = fCut2; |
| } |
| |
| return aRetval; |
| } |
| |
| bool isPointOnEdge( |
| const B2DPoint& rPoint, |
| const B2DPoint& rEdgeStart, |
| const B2DVector& rEdgeDelta, |
| double* pCut) |
| { |
| bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX())); |
| bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY())); |
| const double fZero(0.0); |
| const double fOne(1.0); |
| |
| if(bDeltaXIsZero && bDeltaYIsZero) |
| { |
| // no line, just a point |
| return false; |
| } |
| else if(bDeltaXIsZero) |
| { |
| // vertical line |
| if(fTools::equal(rPoint.getX(), rEdgeStart.getX())) |
| { |
| double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY(); |
| |
| if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne)) |
| { |
| if(pCut) |
| { |
| *pCut = fValue; |
| } |
| |
| return true; |
| } |
| } |
| } |
| else if(bDeltaYIsZero) |
| { |
| // horizontal line |
| if(fTools::equal(rPoint.getY(), rEdgeStart.getY())) |
| { |
| double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX(); |
| |
| if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne)) |
| { |
| if(pCut) |
| { |
| *pCut = fValue; |
| } |
| |
| return true; |
| } |
| } |
| } |
| else |
| { |
| // any angle line |
| double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX(); |
| double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY(); |
| |
| if(fTools::equal(fTOne, fTTwo)) |
| { |
| // same parameter representation, point is on line. Take |
| // middle value for better results |
| double fValue = (fTOne + fTTwo) / 2.0; |
| |
| if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne)) |
| { |
| // point is inside line bounds, too |
| if(pCut) |
| { |
| *pCut = fValue; |
| } |
| |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void applyLineDashing(const B2DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fDotDashLength) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| const sal_uInt32 nDotDashCount(rDotDashArray.size()); |
| |
| if(fTools::lessOrEqual(fDotDashLength, 0.0)) |
| { |
| fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); |
| } |
| |
| if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount) |
| { |
| // clear targets |
| if(pLineTarget) |
| { |
| pLineTarget->clear(); |
| } |
| |
| if(pGapTarget) |
| { |
| pGapTarget->clear(); |
| } |
| |
| // prepare current edge's start |
| B2DCubicBezier aCurrentEdge; |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| // prepare DotDashArray iteration and the line/gap switching bool |
| sal_uInt32 nDotDashIndex(0); |
| bool bIsLine(true); |
| double fDotDashMovingLength(rDotDashArray[0]); |
| B2DPolygon aSnippet; |
| |
| // iterate over all edges |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| // update current edge (fill in C1, C2 and end point) |
| double fLastDotDashMovingLength(0.0); |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| |
| // check if we have a trivial bezier segment -> possible fallback to edge |
| aCurrentEdge.testAndSolveTrivialBezier(); |
| |
| if(aCurrentEdge.isBezier()) |
| { |
| // bezier segment |
| const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge); |
| const double fEdgeLength(aCubicBezierHelper.getLength()); |
| |
| if(!fTools::equalZero(fEdgeLength)) |
| { |
| while(fTools::less(fDotDashMovingLength, fEdgeLength)) |
| { |
| // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] |
| const bool bHandleLine(bIsLine && pLineTarget); |
| const bool bHandleGap(!bIsLine && pGapTarget); |
| |
| if(bHandleLine || bHandleGap) |
| { |
| const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); |
| const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength)); |
| B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd)); |
| |
| if(!aSnippet.count()) |
| { |
| aSnippet.append(aBezierSnippet.getStartPoint()); |
| } |
| |
| aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint()); |
| |
| if(bHandleLine) |
| { |
| pLineTarget->append(aSnippet); |
| } |
| else |
| { |
| pGapTarget->append(aSnippet); |
| } |
| |
| aSnippet.clear(); |
| } |
| |
| // prepare next DotDashArray step and flip line/gap flag |
| fLastDotDashMovingLength = fDotDashMovingLength; |
| fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; |
| bIsLine = !bIsLine; |
| } |
| |
| // append closing snippet [fLastDotDashMovingLength, fEdgeLength] |
| const bool bHandleLine(bIsLine && pLineTarget); |
| const bool bHandleGap(!bIsLine && pGapTarget); |
| |
| if(bHandleLine || bHandleGap) |
| { |
| B2DCubicBezier aRight; |
| const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); |
| |
| aCurrentEdge.split(fBezierSplit, 0, &aRight); |
| |
| if(!aSnippet.count()) |
| { |
| aSnippet.append(aRight.getStartPoint()); |
| } |
| |
| aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint()); |
| } |
| |
| // prepare move to next edge |
| fDotDashMovingLength -= fEdgeLength; |
| } |
| } |
| else |
| { |
| // simple edge |
| const double fEdgeLength(aCurrentEdge.getEdgeLength()); |
| |
| if(!fTools::equalZero(fEdgeLength)) |
| { |
| while(fTools::less(fDotDashMovingLength, fEdgeLength)) |
| { |
| // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] |
| const bool bHandleLine(bIsLine && pLineTarget); |
| const bool bHandleGap(!bIsLine && pGapTarget); |
| |
| if(bHandleLine || bHandleGap) |
| { |
| if(!aSnippet.count()) |
| { |
| aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength)); |
| } |
| |
| aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength)); |
| |
| if(bHandleLine) |
| { |
| pLineTarget->append(aSnippet); |
| } |
| else |
| { |
| pGapTarget->append(aSnippet); |
| } |
| |
| aSnippet.clear(); |
| } |
| |
| // prepare next DotDashArray step and flip line/gap flag |
| fLastDotDashMovingLength = fDotDashMovingLength; |
| fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; |
| bIsLine = !bIsLine; |
| } |
| |
| // append snippet [fLastDotDashMovingLength, fEdgeLength] |
| const bool bHandleLine(bIsLine && pLineTarget); |
| const bool bHandleGap(!bIsLine && pGapTarget); |
| |
| if(bHandleLine || bHandleGap) |
| { |
| if(!aSnippet.count()) |
| { |
| aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength)); |
| } |
| |
| aSnippet.append(aCurrentEdge.getEndPoint()); |
| } |
| |
| // prepare move to next edge |
| fDotDashMovingLength -= fEdgeLength; |
| } |
| } |
| |
| // prepare next edge step (end point gets new start point) |
| aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); |
| } |
| |
| // append last intermediate results (if exists) |
| if(aSnippet.count()) |
| { |
| if(bIsLine && pLineTarget) |
| { |
| pLineTarget->append(aSnippet); |
| } |
| else if(!bIsLine && pGapTarget) |
| { |
| pGapTarget->append(aSnippet); |
| } |
| } |
| |
| // check if start and end polygon may be merged |
| if(pLineTarget) |
| { |
| const sal_uInt32 nCount(pLineTarget->count()); |
| |
| if(nCount > 1) |
| { |
| // these polygons were created above, there exists none with less than two points, |
| // thus dircet point access below is allowed |
| const B2DPolygon aFirst(pLineTarget->getB2DPolygon(0)); |
| B2DPolygon aLast(pLineTarget->getB2DPolygon(nCount - 1)); |
| |
| if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1))) |
| { |
| // start of first and end of last are the same -> merge them |
| aLast.append(aFirst); |
| aLast.removeDoublePoints(); |
| pLineTarget->setB2DPolygon(0, aLast); |
| pLineTarget->remove(nCount - 1); |
| } |
| } |
| } |
| |
| if(pGapTarget) |
| { |
| const sal_uInt32 nCount(pGapTarget->count()); |
| |
| if(nCount > 1) |
| { |
| // these polygons were created above, there exists none with less than two points, |
| // thus dircet point access below is allowed |
| const B2DPolygon aFirst(pGapTarget->getB2DPolygon(0)); |
| B2DPolygon aLast(pGapTarget->getB2DPolygon(nCount - 1)); |
| |
| if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1))) |
| { |
| // start of first and end of last are the same -> merge them |
| aLast.append(aFirst); |
| aLast.removeDoublePoints(); |
| pGapTarget->setB2DPolygon(0, aLast); |
| pGapTarget->remove(nCount - 1); |
| } |
| } |
| } |
| } |
| else |
| { |
| // parameters make no sense, just add source to targets |
| if(pLineTarget) |
| { |
| pLineTarget->append(rCandidate); |
| } |
| |
| if(pGapTarget) |
| { |
| pGapTarget->append(rCandidate); |
| } |
| } |
| } |
| |
| // test if point is inside epsilon-range around an edge defined |
| // by the two given points. Can be used for HitTesting. The epsilon-range |
| // is defined to be the rectangle centered to the given edge, using height |
| // 2 x fDistance, and the circle around both points with radius fDistance. |
| bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance) |
| { |
| // build edge vector |
| const B2DVector aEdge(rEdgeEnd - rEdgeStart); |
| bool bDoDistanceTestStart(false); |
| bool bDoDistanceTestEnd(false); |
| |
| if(aEdge.equalZero()) |
| { |
| // no edge, just a point. Do one of the distance tests. |
| bDoDistanceTestStart = true; |
| } |
| else |
| { |
| // edge has a length. Create perpendicular vector. |
| const B2DVector aPerpend(getPerpendicular(aEdge)); |
| double fCut( |
| (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX()) |
| + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) / |
| (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY())); |
| const double fZero(0.0); |
| const double fOne(1.0); |
| |
| if(fTools::less(fCut, fZero)) |
| { |
| // left of rEdgeStart |
| bDoDistanceTestStart = true; |
| } |
| else if(fTools::more(fCut, fOne)) |
| { |
| // right of rEdgeEnd |
| bDoDistanceTestEnd = true; |
| } |
| else |
| { |
| // inside line [0.0 .. 1.0] |
| const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut)); |
| const B2DVector aDelta(rTestPosition - aCutPoint); |
| const double fDistanceSquare(aDelta.scalar(aDelta)); |
| |
| if(fDistanceSquare <= fDistance * fDistance) |
| { |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| } |
| |
| if(bDoDistanceTestStart) |
| { |
| const B2DVector aDelta(rTestPosition - rEdgeStart); |
| const double fDistanceSquare(aDelta.scalar(aDelta)); |
| |
| if(fDistanceSquare <= fDistance * fDistance) |
| { |
| return true; |
| } |
| } |
| else if(bDoDistanceTestEnd) |
| { |
| const B2DVector aDelta(rTestPosition - rEdgeEnd); |
| const double fDistanceSquare(aDelta.scalar(aDelta)); |
| |
| if(fDistanceSquare <= fDistance * fDistance) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // test if point is inside epsilon-range around the given Polygon. Can be used |
| // for HitTesting. The epsilon-range is defined to be the tube around the polygon |
| // with distance fDistance and rounded edges (start and end point). |
| bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance) |
| { |
| // force to non-bezier polygon |
| const B2DPolygon aCandidate(rCandidate.getDefaultAdaptiveSubdivision()); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| |
| if(nPointCount) |
| { |
| const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| B2DPoint aCurrent(aCandidate.getB2DPoint(0)); |
| |
| if(nEdgeCount) |
| { |
| // edges |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); |
| |
| if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance)) |
| { |
| return true; |
| } |
| |
| // prepare next step |
| aCurrent = aNext; |
| } |
| } |
| else |
| { |
| // no edges, but points -> not closed. Check single point. Just |
| // use isInEpsilonRange with twice the same point, it handles those well |
| if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance)) |
| { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadius ) |
| { |
| const double fZero(0.0); |
| const double fOne(1.0); |
| |
| if(fTools::lessOrEqual(fRadius, fZero)) |
| { |
| // no radius, use rectangle |
| return createPolygonFromRect( rRect ); |
| } |
| else if(fTools::moreOrEqual(fRadius, fOne)) |
| { |
| // full radius, use ellipse |
| const B2DPoint aCenter(rRect.getCenter()); |
| const double fRadiusX(rRect.getWidth() / 2.0); |
| const double fRadiusY(rRect.getHeight() / 2.0); |
| |
| return createPolygonFromEllipse( aCenter, fRadiusX, fRadiusY ); |
| } |
| else |
| { |
| // create rectangle with two radii between ]0.0 .. 1.0[ |
| return createPolygonFromRect( rRect, fRadius, fRadius ); |
| } |
| } |
| |
| B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY ) |
| { |
| const double fZero(0.0); |
| const double fOne(1.0); |
| |
| // crop to useful values |
| if(fTools::less(fRadiusX, fZero)) |
| { |
| fRadiusX = fZero; |
| } |
| else if(fTools::more(fRadiusX, fOne)) |
| { |
| fRadiusX = fOne; |
| } |
| |
| if(fTools::less(fRadiusY, fZero)) |
| { |
| fRadiusY = fZero; |
| } |
| else if(fTools::more(fRadiusY, fOne)) |
| { |
| fRadiusY = fOne; |
| } |
| |
| if(fZero == fRadiusX || fZero == fRadiusY) |
| { |
| B2DPolygon aRetval; |
| |
| // at least in one direction no radius, use rectangle. |
| // Do not use createPolygonFromRect() here since original |
| // creator (historical reasons) still creates a start point at the |
| // bottom center, so do the same here to get the same line patterns. |
| // Due to this the order of points is different, too. |
| const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY()); |
| aRetval.append(aBottomCenter); |
| |
| aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMaxY() ) ); |
| aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMinY() ) ); |
| aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMinY() ) ); |
| aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMaxY() ) ); |
| |
| // close |
| aRetval.setClosed( true ); |
| |
| return aRetval; |
| } |
| else if(fOne == fRadiusX && fOne == fRadiusY) |
| { |
| // in both directions full radius, use ellipse |
| const B2DPoint aCenter(rRect.getCenter()); |
| const double fRectRadiusX(rRect.getWidth() / 2.0); |
| const double fRectRadiusY(rRect.getHeight() / 2.0); |
| |
| return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY ); |
| } |
| else |
| { |
| B2DPolygon aRetval; |
| const double fBowX((rRect.getWidth() / 2.0) * fRadiusX); |
| const double fBowY((rRect.getHeight() / 2.0) * fRadiusY); |
| const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); |
| |
| // create start point at bottom center |
| if(fOne != fRadiusX) |
| { |
| const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY()); |
| aRetval.append(aBottomCenter); |
| } |
| |
| // create first bow |
| { |
| const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY()); |
| const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0)); |
| const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY)); |
| aRetval.append(aStart); |
| aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop); |
| } |
| |
| // create second bow |
| { |
| const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY()); |
| const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY)); |
| const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0)); |
| aRetval.append(aStart); |
| aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop); |
| } |
| |
| // create third bow |
| { |
| const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY()); |
| const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0)); |
| const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY)); |
| aRetval.append(aStart); |
| aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop); |
| } |
| |
| // create forth bow |
| { |
| const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY()); |
| const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY)); |
| const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0)); |
| aRetval.append(aStart); |
| aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop); |
| } |
| |
| // close |
| aRetval.setClosed( true ); |
| |
| // remove double created points if there are extreme radii envolved |
| if(fOne == fRadiusX || fOne == fRadiusY) |
| { |
| aRetval.removeDoublePoints(); |
| } |
| |
| return aRetval; |
| } |
| } |
| |
| B2DPolygon createPolygonFromRect( const B2DRectangle& rRect ) |
| { |
| B2DPolygon aRetval; |
| |
| aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMinY() ) ); |
| aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMinY() ) ); |
| aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMaxY() ) ); |
| aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMaxY() ) ); |
| |
| // close |
| aRetval.setClosed( true ); |
| |
| return aRetval; |
| } |
| |
| B2DPolygon createUnitPolygon() |
| { |
| static B2DPolygon aRetval; |
| |
| if(!aRetval.count()) |
| { |
| aRetval.append( B2DPoint( 0.0, 0.0 ) ); |
| aRetval.append( B2DPoint( 1.0, 0.0 ) ); |
| aRetval.append( B2DPoint( 1.0, 1.0 ) ); |
| aRetval.append( B2DPoint( 0.0, 1.0 ) ); |
| |
| // close |
| aRetval.setClosed( true ); |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius ) |
| { |
| return createPolygonFromEllipse( rCenter, fRadius, fRadius ); |
| } |
| |
| B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant) |
| { |
| B2DPolygon aUnitCircle; |
| const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); |
| const double fScaledKappa(fKappa * (1.0 / STEPSPERQUARTER)); |
| const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI2 / STEPSPERQUARTER)); |
| |
| B2DPoint aPoint(1.0, 0.0); |
| B2DPoint aForward(1.0, fScaledKappa); |
| B2DPoint aBackward(1.0, -fScaledKappa); |
| |
| if(0 != nStartQuadrant) |
| { |
| const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(F_PI2 * (nStartQuadrant % 4))); |
| aPoint *= aQuadrantMatrix; |
| aBackward *= aQuadrantMatrix; |
| aForward *= aQuadrantMatrix; |
| } |
| |
| aUnitCircle.append(aPoint); |
| |
| for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++) |
| { |
| aPoint *= aRotateMatrix; |
| aBackward *= aRotateMatrix; |
| aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint); |
| aForward *= aRotateMatrix; |
| } |
| |
| aUnitCircle.setClosed(true); |
| aUnitCircle.removeDoublePoints(); |
| |
| return aUnitCircle; |
| } |
| |
| B2DPolygon createHalfUnitCircle() |
| { |
| static B2DPolygon aUnitHalfCircle; |
| |
| if(!aUnitHalfCircle.count()) |
| { |
| const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); |
| const double fScaledKappa(fKappa * (1.0 / STEPSPERQUARTER)); |
| const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI2 / STEPSPERQUARTER)); |
| B2DPoint aPoint(1.0, 0.0); |
| B2DPoint aForward(1.0, fScaledKappa); |
| B2DPoint aBackward(1.0, -fScaledKappa); |
| |
| aUnitHalfCircle.append(aPoint); |
| |
| for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++) |
| { |
| aPoint *= aRotateMatrix; |
| aBackward *= aRotateMatrix; |
| aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint); |
| aForward *= aRotateMatrix; |
| } |
| } |
| |
| return aUnitHalfCircle; |
| } |
| |
| B2DPolygon createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant) |
| { |
| switch(nStartQuadrant % 4) |
| { |
| case 1 : |
| { |
| static B2DPolygon aUnitCircleStartQuadrantOne; |
| |
| if(!aUnitCircleStartQuadrantOne.count()) |
| { |
| ::osl::Mutex m_mutex; |
| aUnitCircleStartQuadrantOne = impCreateUnitCircle(1); |
| } |
| |
| return aUnitCircleStartQuadrantOne; |
| } |
| case 2 : |
| { |
| static B2DPolygon aUnitCircleStartQuadrantTwo; |
| |
| if(!aUnitCircleStartQuadrantTwo.count()) |
| { |
| ::osl::Mutex m_mutex; |
| aUnitCircleStartQuadrantTwo = impCreateUnitCircle(2); |
| } |
| |
| return aUnitCircleStartQuadrantTwo; |
| } |
| case 3 : |
| { |
| static B2DPolygon aUnitCircleStartQuadrantThree; |
| |
| if(!aUnitCircleStartQuadrantThree.count()) |
| { |
| ::osl::Mutex m_mutex; |
| aUnitCircleStartQuadrantThree = impCreateUnitCircle(3); |
| } |
| |
| return aUnitCircleStartQuadrantThree; |
| } |
| default : // case 0 : |
| { |
| static B2DPolygon aUnitCircleStartQuadrantZero; |
| |
| if(!aUnitCircleStartQuadrantZero.count()) |
| { |
| ::osl::Mutex m_mutex; |
| aUnitCircleStartQuadrantZero = impCreateUnitCircle(0); |
| } |
| |
| return aUnitCircleStartQuadrantZero; |
| } |
| } |
| } |
| |
| B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY ) |
| { |
| B2DPolygon aRetval(createPolygonFromUnitCircle()); |
| const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY())); |
| |
| aRetval.transform(aMatrix); |
| |
| return aRetval; |
| } |
| |
| B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd ) |
| { |
| B2DPolygon aRetval; |
| |
| // truncate fStart, fEnd to a range of [0.0 .. F_2PI[ where F_2PI |
| // falls back to 0.0 to ensure a unique definition |
| if(fTools::less(fStart, 0.0)) |
| { |
| fStart = 0.0; |
| } |
| |
| if(fTools::moreOrEqual(fStart, F_2PI)) |
| { |
| fStart = 0.0; |
| } |
| |
| if(fTools::less(fEnd, 0.0)) |
| { |
| fEnd = 0.0; |
| } |
| |
| if(fTools::moreOrEqual(fEnd, F_2PI)) |
| { |
| fEnd = 0.0; |
| } |
| |
| if(fTools::equal(fStart, fEnd)) |
| { |
| // same start and end angle, add single point |
| aRetval.append(B2DPoint(cos(fStart), sin(fStart))); |
| } |
| else |
| { |
| const sal_uInt32 nSegments(STEPSPERQUARTER * 4); |
| const double fAnglePerSegment(F_PI2 / STEPSPERQUARTER); |
| const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments); |
| const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments); |
| const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); |
| const double fScaledKappa(fKappa * (1.0 / STEPSPERQUARTER)); |
| |
| B2DPoint aSegStart(cos(fStart), sin(fStart)); |
| aRetval.append(aSegStart); |
| |
| if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart)) |
| { |
| // start and end in one sector and in the right order, create in one segment |
| const B2DPoint aSegEnd(cos(fEnd), sin(fEnd)); |
| const double fFactor(fScaledKappa * ((fEnd - fStart) / fAnglePerSegment)); |
| |
| aRetval.appendBezierSegment( |
| aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor), |
| aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor), |
| aSegEnd); |
| } |
| else |
| { |
| double fSegEndRad((nStartSegment + 1) * fAnglePerSegment); |
| double fFactor(fScaledKappa * ((fSegEndRad - fStart) / fAnglePerSegment)); |
| B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad)); |
| |
| aRetval.appendBezierSegment( |
| aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor), |
| aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor), |
| aSegEnd); |
| |
| sal_uInt32 nSegment((nStartSegment + 1) % nSegments); |
| aSegStart = aSegEnd; |
| |
| while(nSegment != nEndSegment) |
| { |
| // No end in this sector, add full sector. |
| fSegEndRad = (nSegment + 1) * fAnglePerSegment; |
| aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad)); |
| |
| aRetval.appendBezierSegment( |
| aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fScaledKappa), |
| aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fScaledKappa), |
| aSegEnd); |
| |
| nSegment = (nSegment + 1) % nSegments; |
| aSegStart = aSegEnd; |
| } |
| |
| // End in this sector |
| const double fSegStartRad(nSegment * fAnglePerSegment); |
| fFactor = fScaledKappa * ((fEnd - fSegStartRad) / fAnglePerSegment); |
| aSegEnd = B2DPoint(cos(fEnd), sin(fEnd)); |
| |
| aRetval.appendBezierSegment( |
| aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor), |
| aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor), |
| aSegEnd); |
| } |
| } |
| |
| // remove double points between segments created by segmented creation |
| aRetval.removeDoublePoints(); |
| |
| return aRetval; |
| } |
| |
| B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd ) |
| { |
| B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd)); |
| const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY())); |
| |
| aRetval.transform(aMatrix); |
| |
| return aRetval; |
| } |
| |
| bool hasNeutralPoints(const B2DPolygon& rCandidate) |
| { |
| OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)"); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount > 2L) |
| { |
| B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L)); |
| B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L)); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); |
| const B2DVector aPrevVec(aPrevPoint - aCurrPoint); |
| const B2DVector aNextVec(aNextPoint - aCurrPoint); |
| const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec)); |
| |
| if(ORIENTATION_NEUTRAL == aOrientation) |
| { |
| // current has neutral orientation |
| return true; |
| } |
| else |
| { |
| // prepare next |
| aPrevPoint = aCurrPoint; |
| aCurrPoint = aNextPoint; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate) |
| { |
| if(hasNeutralPoints(rCandidate)) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DPolygon aRetval; |
| B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L)); |
| B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L)); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); |
| const B2DVector aPrevVec(aPrevPoint - aCurrPoint); |
| const B2DVector aNextVec(aNextPoint - aCurrPoint); |
| const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec)); |
| |
| if(ORIENTATION_NEUTRAL == aOrientation) |
| { |
| // current has neutral orientation, leave it out and prepare next |
| aCurrPoint = aNextPoint; |
| } |
| else |
| { |
| // add current point |
| aRetval.append(aCurrPoint); |
| |
| // prepare next |
| aPrevPoint = aCurrPoint; |
| aCurrPoint = aNextPoint; |
| } |
| } |
| |
| while(aRetval.count() && ORIENTATION_NEUTRAL == getOrientationForIndex(aRetval, 0L)) |
| { |
| aRetval.remove(0L); |
| } |
| |
| // copy closed state |
| aRetval.setClosed(rCandidate.isClosed()); |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| bool isConvex(const B2DPolygon& rCandidate) |
| { |
| OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)"); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount > 2L) |
| { |
| const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L)); |
| B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L)); |
| B2DVector aCurrVec(aPrevPoint - aCurrPoint); |
| B2VectorOrientation aOrientation(ORIENTATION_NEUTRAL); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); |
| const B2DVector aNextVec(aNextPoint - aCurrPoint); |
| const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec)); |
| |
| if(ORIENTATION_NEUTRAL == aOrientation) |
| { |
| // set start value, maybe neutral again |
| aOrientation = aCurrentOrientation; |
| } |
| else |
| { |
| if(ORIENTATION_NEUTRAL != aCurrentOrientation && aCurrentOrientation != aOrientation) |
| { |
| // different orientations found, that's it |
| return false; |
| } |
| } |
| |
| // prepare next |
| aCurrPoint = aNextPoint; |
| aCurrVec = -aNextVec; |
| } |
| } |
| |
| return true; |
| } |
| |
| B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)"); |
| const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate))); |
| const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex)); |
| const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate))); |
| const B2DVector aBack(aPrev - aCurr); |
| const B2DVector aForw(aNext - aCurr); |
| |
| return getOrientation(aForw, aBack); |
| } |
| |
| bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints) |
| { |
| if(rCandidate.equal(rStart) || rCandidate.equal(rEnd)) |
| { |
| // candidate is in epsilon around start or end -> inside |
| return bWithPoints; |
| } |
| else if(rStart.equal(rEnd)) |
| { |
| // start and end are equal, but candidate is outside their epsilon -> outside |
| return false; |
| } |
| else |
| { |
| const B2DVector aEdgeVector(rEnd - rStart); |
| const B2DVector aTestVector(rCandidate - rStart); |
| |
| if(areParallel(aEdgeVector, aTestVector)) |
| { |
| const double fZero(0.0); |
| const double fOne(1.0); |
| const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY()) |
| ? aTestVector.getX() / aEdgeVector.getX() |
| : aTestVector.getY() / aEdgeVector.getY()); |
| |
| if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| } |
| |
| bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints) |
| { |
| const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| |
| if(nPointCount > 1L) |
| { |
| const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0L)); |
| |
| for(sal_uInt32 a(0L); a < nLoopCount; a++) |
| { |
| const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1L) % nPointCount)); |
| |
| if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints)) |
| { |
| return true; |
| } |
| |
| aCurrentPoint = aNextPoint; |
| } |
| } |
| else if(nPointCount && bWithPoints) |
| { |
| return rPoint.equal(aCandidate.getB2DPoint(0L)); |
| } |
| |
| return false; |
| } |
| |
| bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder) |
| { |
| if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder)) |
| { |
| if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder)) |
| { |
| if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder)) |
| { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine) |
| { |
| const B2DVector aLineVector(rEnd - rStart); |
| const B2DVector aVectorToA(rEnd - rCandidateA); |
| const double fCrossA(aLineVector.cross(aVectorToA)); |
| |
| if(fTools::equalZero(fCrossA)) |
| { |
| // one point on the line |
| return bWithLine; |
| } |
| |
| const B2DVector aVectorToB(rEnd - rCandidateB); |
| const double fCrossB(aLineVector.cross(aVectorToB)); |
| |
| if(fTools::equalZero(fCrossB)) |
| { |
| // one point on the line |
| return bWithLine; |
| } |
| |
| // return true if they both have the same sign |
| return ((fCrossA > 0.0) == (fCrossB > 0.0)); |
| } |
| |
| void addTriangleFan(const B2DPolygon& rCandidate, B2DPolygon& rTarget) |
| { |
| const sal_uInt32 nCount(rCandidate.count()); |
| |
| if(nCount > 2L) |
| { |
| const B2DPoint aStart(rCandidate.getB2DPoint(0L)); |
| B2DPoint aLast(rCandidate.getB2DPoint(1L)); |
| |
| for(sal_uInt32 a(2L); a < nCount; a++) |
| { |
| const B2DPoint aCurrent(rCandidate.getB2DPoint(a)); |
| rTarget.append(aStart); |
| rTarget.append(aLast); |
| rTarget.append(aCurrent); |
| |
| // prepare next |
| aLast = aCurrent; |
| } |
| } |
| } |
| |
| namespace |
| { |
| /// return 0 for input of 0, -1 for negative and 1 for positive input |
| inline int lcl_sgn( const double n ) |
| { |
| return n == 0.0 ? 0 : 1 - 2*::rtl::math::isSignBitSet(n); |
| } |
| } |
| |
| bool isRectangle( const B2DPolygon& rPoly ) |
| { |
| // polygon must be closed to resemble a rect, and contain |
| // at least four points. |
| if( !rPoly.isClosed() || |
| rPoly.count() < 4 || |
| rPoly.areControlPointsUsed() ) |
| { |
| return false; |
| } |
| |
| // number of 90 degree turns the polygon has taken |
| int nNumTurns(0); |
| |
| int nVerticalEdgeType=0; |
| int nHorizontalEdgeType=0; |
| bool bNullVertex(true); |
| bool bCWPolygon(false); // when true, polygon is CW |
| // oriented, when false, CCW |
| bool bOrientationSet(false); // when false, polygon |
| // orientation has not yet |
| // been determined. |
| |
| // scan all _edges_ (which involves coming back to point 0 |
| // for the last edge - thus the modulo operation below) |
| const sal_Int32 nCount( rPoly.count() ); |
| for( sal_Int32 i=0; i<nCount; ++i ) |
| { |
| const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) ); |
| const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) ); |
| |
| // is 0 for zero direction vector, 1 for south edge and -1 |
| // for north edge (standard screen coordinate system) |
| int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) ); |
| |
| // is 0 for zero direction vector, 1 for east edge and -1 |
| // for west edge (standard screen coordinate system) |
| int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) ); |
| |
| if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType ) |
| return false; // oblique edge - for sure no rect |
| |
| const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType ); |
| |
| // current vertex is equal to previous - just skip, |
| // until we have a real edge |
| if( bCurrNullVertex ) |
| continue; |
| |
| // if previous edge has two identical points, because |
| // no previous edge direction was available, simply |
| // take this first non-null edge as the start |
| // direction. That's what will happen here, if |
| // bNullVertex is false |
| if( !bNullVertex ) |
| { |
| // 2D cross product - is 1 for CW and -1 for CCW turns |
| const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType - |
| nVerticalEdgeType*nCurrHorizontalEdgeType ); |
| |
| if( !nCrossProduct ) |
| continue; // no change in orientation - |
| // collinear edges - just go on |
| |
| // if polygon orientation is not set, we'll |
| // determine it now |
| if( !bOrientationSet ) |
| { |
| bCWPolygon = nCrossProduct == 1; |
| bOrientationSet = true; |
| } |
| else |
| { |
| // if current turn orientation is not equal |
| // initial orientation, this is not a |
| // rectangle (as rectangles have consistent |
| // orientation). |
| if( (nCrossProduct == 1) != bCWPolygon ) |
| return false; |
| } |
| |
| ++nNumTurns; |
| |
| // More than four 90 degree turns are an |
| // indication that this must not be a rectangle. |
| if( nNumTurns > 4 ) |
| return false; |
| } |
| |
| // store current state for the next turn |
| nVerticalEdgeType = nCurrVerticalEdgeType; |
| nHorizontalEdgeType = nCurrHorizontalEdgeType; |
| bNullVertex = false; // won't reach this line, |
| // if bCurrNullVertex is |
| // true - see above |
| } |
| |
| return true; |
| } |
| |
| B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| // call myself recursively with subdivided input |
| const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); |
| return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate); |
| } |
| else |
| { |
| B3DPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| B2DPoint aPoint(rCandidate.getB2DPoint(a)); |
| aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate)); |
| } |
| |
| // copy closed state |
| aRetval.setClosed(rCandidate.isClosed()); |
| |
| return aRetval; |
| } |
| } |
| |
| B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat) |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nCount(rCandidate.count()); |
| const bool bIsIdentity(rMat.isIdentity()); |
| |
| for(sal_uInt32 a(0L); a < nCount; a++) |
| { |
| B3DPoint aCandidate(rCandidate.getB3DPoint(a)); |
| |
| if(!bIsIdentity) |
| { |
| aCandidate *= rMat; |
| } |
| |
| aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY())); |
| } |
| |
| // copy closed state |
| aRetval.setClosed(rCandidate.isClosed()); |
| |
| return aRetval; |
| } |
| |
| double getDistancePointToEndlessRay(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) |
| { |
| if(rPointA.equal(rPointB)) |
| { |
| rCut = 0.0; |
| const B2DVector aVector(rTestPoint - rPointA); |
| return aVector.getLength(); |
| } |
| else |
| { |
| // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint |
| const B2DVector aVector1(rPointB - rPointA); |
| const B2DVector aVector2(rTestPoint - rPointA); |
| const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY())); |
| const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY())); |
| |
| rCut = fDividend / fDivisor; |
| |
| const B2DPoint aCutPoint(rPointA + rCut * aVector1); |
| const B2DVector aVector(rTestPoint - aCutPoint); |
| return aVector.getLength(); |
| } |
| } |
| |
| double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) |
| { |
| if(rPointA.equal(rPointB)) |
| { |
| rCut = 0.0; |
| const B2DVector aVector(rTestPoint - rPointA); |
| return aVector.getLength(); |
| } |
| else |
| { |
| // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint |
| const B2DVector aVector1(rPointB - rPointA); |
| const B2DVector aVector2(rTestPoint - rPointA); |
| const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY())); |
| const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY())); |
| const double fCut(fDividend / fDivisor); |
| |
| if(fCut < 0.0) |
| { |
| // not in line range, get distance to PointA |
| rCut = 0.0; |
| return aVector2.getLength(); |
| } |
| else if(fCut > 1.0) |
| { |
| // not in line range, get distance to PointB |
| rCut = 1.0; |
| const B2DVector aVector(rTestPoint - rPointB); |
| return aVector.getLength(); |
| } |
| else |
| { |
| // in line range |
| const B2DPoint aCutPoint(rPointA + fCut * aVector1); |
| const B2DVector aVector(rTestPoint - aCutPoint); |
| rCut = fCut; |
| return aVector.getLength(); |
| } |
| } |
| } |
| |
| double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut) |
| { |
| double fRetval(DBL_MAX); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount > 1L) |
| { |
| const double fZero(0.0); |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| B2DCubicBezier aBezier; |
| aBezier.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| double fEdgeDist; |
| double fNewCut; |
| bool bEdgeIsCurve(false); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aBezier.testAndSolveTrivialBezier(); |
| bEdgeIsCurve = aBezier.isBezier(); |
| } |
| |
| if(bEdgeIsCurve) |
| { |
| fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut); |
| } |
| else |
| { |
| fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut); |
| } |
| |
| if(DBL_MAX == fRetval || fEdgeDist < fRetval) |
| { |
| fRetval = fEdgeDist; |
| rEdgeIndex = a; |
| rCut = fNewCut; |
| |
| if(fTools::equal(fRetval, fZero)) |
| { |
| // already found zero distance, cannot get better. Ensure numerical zero value and end loop. |
| fRetval = 0.0; |
| break; |
| } |
| } |
| |
| // prepare next step |
| aBezier.setStartPoint(aBezier.getEndPoint()); |
| } |
| |
| if(1.0 == rCut) |
| { |
| // correct rEdgeIndex when not last point |
| if(rCandidate.isClosed()) |
| { |
| rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate); |
| rCut = 0.0; |
| } |
| else |
| { |
| if(rEdgeIndex != nEdgeCount - 1L) |
| { |
| rEdgeIndex++; |
| rCut = 0.0; |
| } |
| } |
| } |
| } |
| |
| return fRetval; |
| } |
| |
| B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight) |
| { |
| if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight())) |
| { |
| return rCandidate; |
| } |
| else |
| { |
| const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth()); |
| const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight()); |
| const double fOneMinusRelativeX(1.0 - fRelativeX); |
| const double fOneMinusRelativeY(1.0 - fRelativeY); |
| const double fNewX((fOneMinusRelativeY) * ((fOneMinusRelativeX) * rTopLeft.getX() + fRelativeX * rTopRight.getX()) + |
| fRelativeY * ((fOneMinusRelativeX) * rBottomLeft.getX() + fRelativeX * rBottomRight.getX())); |
| const double fNewY((fOneMinusRelativeX) * ((fOneMinusRelativeY) * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) + |
| fRelativeX * ((fOneMinusRelativeY) * rTopRight.getY() + fRelativeY * rBottomRight.getY())); |
| |
| return B2DPoint(fNewX, fNewY); |
| } |
| } |
| |
| B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount && 0.0 != rOriginal.getWidth() && 0.0 != rOriginal.getHeight()) |
| { |
| B2DPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| if(!rCandidate.getPrevControlPoint(a).equalZero()) |
| { |
| aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); |
| } |
| |
| if(!rCandidate.getNextControlPoint(a).equalZero()) |
| { |
| aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); |
| } |
| } |
| } |
| |
| aRetval.setClosed(rCandidate.isClosed()); |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| B2DPolygon rotateAroundPoint(const B2DPolygon& rCandidate, const B2DPoint& rCenter, double fAngle) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| B2DPolygon aRetval(rCandidate); |
| |
| if(nPointCount) |
| { |
| const B2DHomMatrix aMatrix(basegfx::tools::createRotateAroundPoint(rCenter, fAngle)); |
| |
| aRetval.transform(aMatrix); |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolygon expandToCurve(const B2DPolygon& rCandidate) |
| { |
| B2DPolygon aRetval(rCandidate); |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| expandToCurveInPoint(aRetval, a); |
| } |
| |
| return aRetval; |
| } |
| |
| bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)"); |
| bool bRetval(false); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount) |
| { |
| // predecessor |
| if(!rCandidate.isPrevControlPointUsed(nIndex)) |
| { |
| if(!rCandidate.isClosed() && 0 == nIndex) |
| { |
| // do not create previous vector for start point of open polygon |
| } |
| else |
| { |
| const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); |
| rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); |
| bRetval = true; |
| } |
| } |
| |
| // successor |
| if(!rCandidate.isNextControlPointUsed(nIndex)) |
| { |
| if(!rCandidate.isClosed() && nIndex + 1 == nPointCount) |
| { |
| // do not create next vector for end point of open polygon |
| } |
| else |
| { |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); |
| bRetval = true; |
| } |
| } |
| } |
| |
| return bRetval; |
| } |
| |
| B2DPolygon setContinuity(const B2DPolygon& rCandidate, B2VectorContinuity eContinuity) |
| { |
| B2DPolygon aRetval(rCandidate); |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| setContinuityInPoint(aRetval, a, eContinuity); |
| } |
| |
| return aRetval; |
| } |
| |
| bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity) |
| { |
| OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)"); |
| bool bRetval(false); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount) |
| { |
| const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex)); |
| |
| switch(eContinuity) |
| { |
| case CONTINUITY_NONE : |
| { |
| if(rCandidate.isPrevControlPointUsed(nIndex)) |
| { |
| if(!rCandidate.isClosed() && 0 == nIndex) |
| { |
| // remove existing previous vector for start point of open polygon |
| rCandidate.resetPrevControlPoint(nIndex); |
| } |
| else |
| { |
| const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); |
| rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); |
| } |
| |
| bRetval = true; |
| } |
| |
| if(rCandidate.isNextControlPointUsed(nIndex)) |
| { |
| if(!rCandidate.isClosed() && nIndex == nPointCount + 1) |
| { |
| // remove next vector for end point of open polygon |
| rCandidate.resetNextControlPoint(nIndex); |
| } |
| else |
| { |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); |
| } |
| |
| bRetval = true; |
| } |
| |
| break; |
| } |
| case CONTINUITY_C1 : |
| { |
| if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) |
| { |
| // lengths both exist since both are used |
| B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); |
| B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); |
| const double fLenPrev(aVectorPrev.getLength()); |
| const double fLenNext(aVectorNext.getLength()); |
| aVectorPrev.normalize(); |
| aVectorNext.normalize(); |
| const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); |
| |
| if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) |
| { |
| // parallel and opposite direction; check length |
| if(fTools::equal(fLenPrev, fLenNext)) |
| { |
| // this would be even C2, but we want C1. Use the lengths of the corresponding edges. |
| const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); |
| const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); |
| const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0)); |
| const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0)); |
| |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint + (aVectorPrev * fLenPrevEdge), |
| aCurrentPoint + (aVectorNext * fLenNextEdge)); |
| bRetval = true; |
| } |
| } |
| else |
| { |
| // not parallel or same direction, set vectors and length |
| const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); |
| |
| if(ORIENTATION_POSITIVE == aOrientation) |
| { |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint - (aNormalizedPerpendicular * fLenPrev), |
| aCurrentPoint + (aNormalizedPerpendicular * fLenNext)); |
| } |
| else |
| { |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint + (aNormalizedPerpendicular * fLenPrev), |
| aCurrentPoint - (aNormalizedPerpendicular * fLenNext)); |
| } |
| |
| bRetval = true; |
| } |
| } |
| break; |
| } |
| case CONTINUITY_C2 : |
| { |
| if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) |
| { |
| // lengths both exist since both are used |
| B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); |
| B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); |
| const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0); |
| aVectorPrev.normalize(); |
| aVectorNext.normalize(); |
| const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); |
| |
| if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) |
| { |
| // parallel and opposite direction; set length. Use one direction for better numerical correctness |
| const B2DVector aScaledDirection(aVectorPrev * fCommonLength); |
| |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint + aScaledDirection, |
| aCurrentPoint - aScaledDirection); |
| } |
| else |
| { |
| // not parallel or same direction, set vectors and length |
| const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); |
| const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength); |
| |
| if(ORIENTATION_POSITIVE == aOrientation) |
| { |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint - aPerpendicular, |
| aCurrentPoint + aPerpendicular); |
| } |
| else |
| { |
| rCandidate.setControlPoints(nIndex, |
| aCurrentPoint + aPerpendicular, |
| aCurrentPoint - aPerpendicular); |
| } |
| } |
| |
| bRetval = true; |
| } |
| break; |
| } |
| } |
| } |
| |
| return bRetval; |
| } |
| |
| B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue) |
| { |
| if(0.0 != fValue) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| // call myself recursively with subdivided input |
| const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); |
| return growInNormalDirection(aCandidate, fValue); |
| } |
| else |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount) |
| { |
| B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1L)); |
| B2DPoint aCurrent(rCandidate.getB2DPoint(0L)); |
| |
| for(sal_uInt32 a(0L); a < nPointCount; a++) |
| { |
| const B2DPoint aNext(rCandidate.getB2DPoint(a + 1L == nPointCount ? 0L : a + 1L)); |
| const B2DVector aBack(aPrev - aCurrent); |
| const B2DVector aForw(aNext - aCurrent); |
| const B2DVector aPerpBack(getNormalizedPerpendicular(aBack)); |
| const B2DVector aPerpForw(getNormalizedPerpendicular(aForw)); |
| B2DVector aDirection(aPerpBack - aPerpForw); |
| aDirection.normalize(); |
| aDirection *= fValue; |
| aRetval.append(aCurrent + aDirection); |
| |
| // prepare next step |
| aPrev = aCurrent; |
| aCurrent = aNext; |
| } |
| } |
| |
| // copy closed state |
| aRetval.setClosed(rCandidate.isClosed()); |
| |
| return aRetval; |
| } |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments) |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount && nSegments) |
| { |
| // get current segment count |
| const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| |
| if(nSegmentCount == nSegments) |
| { |
| aRetval = rCandidate; |
| } |
| else |
| { |
| const double fLength(getLength(rCandidate)); |
| const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1L); |
| |
| for(sal_uInt32 a(0L); a < nLoopCount; a++) |
| { |
| const double fRelativePos((double)a / (double)nSegments); // 0.0 .. 1.0 |
| const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength)); |
| aRetval.append(aNewPoint); |
| } |
| |
| // copy closed flag |
| aRetval.setClosed(rCandidate.isClosed()); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolygon reSegmentPolygonEdges(const B2DPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount < 2 || nSubEdges < 2 || (!bHandleCurvedEdges && !bHandleStraightEdges)) |
| { |
| // nothing to do: |
| // - less than two points -> no edge at all |
| // - less than two nSubEdges -> no resegment necessary |
| // - neither bHandleCurvedEdges nor bHandleStraightEdges -> nothing to do |
| return rCandidate; |
| } |
| else |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DCubicBezier aCurrentEdge; |
| |
| // prepare first edge and add start point to target |
| aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0)); |
| aRetval.append(aCurrentEdge.getStartPoint()); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| // fill edge |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| |
| if(aCurrentEdge.isBezier()) |
| { |
| if(bHandleCurvedEdges) |
| { |
| for(sal_uInt32 b(nSubEdges); b > 1; b--) |
| { |
| const double fSplitPoint(1.0 / b); |
| B2DCubicBezier aLeftPart; |
| |
| aCurrentEdge.split(fSplitPoint, &aLeftPart, &aCurrentEdge); |
| aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), aLeftPart.getEndPoint()); |
| } |
| } |
| |
| // copy remaining segment to target |
| aRetval.appendBezierSegment(aCurrentEdge.getControlPointA(), aCurrentEdge.getControlPointB(), aCurrentEdge.getEndPoint()); |
| } |
| else |
| { |
| if(bHandleStraightEdges) |
| { |
| for(sal_uInt32 b(nSubEdges); b > 1; b--) |
| { |
| const double fSplitPoint(1.0 / b); |
| const B2DPoint aSplitPoint(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fSplitPoint)); |
| |
| aRetval.append(aSplitPoint); |
| aCurrentEdge.setStartPoint(aSplitPoint); |
| } |
| } |
| |
| // copy remaining segment to target |
| aRetval.append(aCurrentEdge.getEndPoint()); |
| } |
| |
| // prepare next step |
| aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); |
| } |
| |
| // copy closed flag and return |
| aRetval.setClosed(rCandidate.isClosed()); |
| return aRetval; |
| } |
| } |
| |
| B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t) |
| { |
| OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)"); |
| |
| if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2) |
| { |
| return rOld1; |
| } |
| else if(fTools::moreOrEqual(t, 1.0)) |
| { |
| return rOld2; |
| } |
| else |
| { |
| B2DPolygon aRetval; |
| const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed()); |
| aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed()); |
| |
| for(sal_uInt32 a(0L); a < rOld1.count(); a++) |
| { |
| aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t)); |
| |
| if(bInterpolateVectors) |
| { |
| aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t)); |
| aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t)); |
| } |
| } |
| |
| return aRetval; |
| } |
| } |
| |
| bool isPolyPolygonEqualRectangle( const B2DPolyPolygon& rPolyPoly, |
| const B2DRange& rRect ) |
| { |
| // exclude some cheap cases first |
| if( rPolyPoly.count() != 1 ) |
| return false; |
| |
| // fill array with rectangle vertices |
| const B2DPoint aPoints[] = |
| { |
| B2DPoint(rRect.getMinX(),rRect.getMinY()), |
| B2DPoint(rRect.getMaxX(),rRect.getMinY()), |
| B2DPoint(rRect.getMaxX(),rRect.getMaxY()), |
| B2DPoint(rRect.getMinX(),rRect.getMaxY()) |
| }; |
| |
| const B2DPolygon& rPoly( rPolyPoly.getB2DPolygon(0) ); |
| const sal_uInt32 nCount( rPoly.count() ); |
| const double epsilon = ::std::numeric_limits<double>::epsilon(); |
| |
| for(unsigned int j=0; j<4; ++j) |
| { |
| const B2DPoint &p1 = aPoints[j]; |
| const B2DPoint &p2 = aPoints[(j+1)%4]; |
| bool bPointOnBoundary = false; |
| for( sal_uInt32 i=0; i<nCount; ++i ) |
| { |
| const B2DPoint p(rPoly.getB2DPoint(i)); |
| |
| // 1 | x0 y0 1 | |
| // A = - | x1 y1 1 | |
| // 2 | x2 y2 1 | |
| double fDoubleArea = p2.getX()*p.getY() - |
| p2.getY()*p.getX() - |
| p1.getX()*p.getY() + |
| p1.getY()*p.getX() + |
| p1.getX()*p2.getY() - |
| p1.getY()*p2.getX(); |
| |
| if(fDoubleArea < epsilon) |
| { |
| bPointOnBoundary=true; |
| break; |
| } |
| } |
| if(!(bPointOnBoundary)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| // create simplified version of the original polygon by |
| // replacing segments with spikes/loops and self intersections |
| // by several trivial sub-segments |
| B2DPolygon createSimplifiedPolygon( const B2DPolygon& rCandidate ) |
| { |
| const sal_uInt32 nCount(rCandidate.count()); |
| |
| if(nCount && rCandidate.areControlPointsUsed()) |
| { |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1); |
| B2DPolygon aRetval; |
| B2DCubicBezier aSegment; |
| |
| aSegment.setStartPoint(rCandidate.getB2DPoint(0)); |
| aRetval.append(aSegment.getStartPoint()); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| // fill edge |
| const sal_uInt32 nNextIndex((a + 1) % nCount); |
| aSegment.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aSegment.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aSegment.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| |
| if(aSegment.isBezier()) |
| { |
| double fExtremumPos(0.0); |
| sal_uInt32 nExtremumCounter(4); |
| |
| while(nExtremumCounter-- && aSegment.isBezier() && aSegment.getMinimumExtremumPosition(fExtremumPos)) |
| { |
| // split off left, now extremum-free part and append |
| B2DCubicBezier aLeft; |
| |
| aSegment.split(fExtremumPos, &aLeft, &aSegment); |
| aLeft.testAndSolveTrivialBezier(); |
| aSegment.testAndSolveTrivialBezier(); |
| |
| if(aLeft.isBezier()) |
| { |
| aRetval.appendBezierSegment(aLeft.getControlPointA(), aLeft.getControlPointB(), aLeft.getEndPoint()); |
| } |
| else |
| { |
| aRetval.append(aLeft.getEndPoint()); |
| } |
| } |
| |
| // append (evtl. reduced) rest of Segment |
| if(aSegment.isBezier()) |
| { |
| aRetval.appendBezierSegment(aSegment.getControlPointA(), aSegment.getControlPointB(), aSegment.getEndPoint()); |
| } |
| else |
| { |
| aRetval.append(aSegment.getEndPoint()); |
| } |
| } |
| else |
| { |
| // simple edge, append end point |
| aRetval.append(aSegment.getEndPoint()); |
| } |
| |
| // prepare next edge |
| aSegment.setStartPoint(aSegment.getEndPoint()); |
| } |
| |
| // copy closed flag and check for double points |
| aRetval.setClosed(rCandidate.isClosed()); |
| aRetval.removeDoublePoints(); |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| // #i76891# |
| B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount && rCandidate.areControlPointsUsed()) |
| { |
| // prepare loop |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DPolygon aRetval; |
| B2DCubicBezier aBezier; |
| aBezier.setStartPoint(rCandidate.getB2DPoint(0)); |
| |
| // try to avoid costly reallocations |
| aRetval.reserve( nEdgeCount+1); |
| |
| // add start point |
| aRetval.append(aBezier.getStartPoint()); |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get values for edge |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); |
| aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); |
| aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); |
| aBezier.testAndSolveTrivialBezier(); |
| |
| // still bezier? |
| if(aBezier.isBezier()) |
| { |
| // add edge with control vectors |
| aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint()); |
| } |
| else |
| { |
| // add edge |
| aRetval.append(aBezier.getEndPoint()); |
| } |
| |
| // next point |
| aBezier.setStartPoint(aBezier.getEndPoint()); |
| } |
| |
| if(rCandidate.isClosed()) |
| { |
| // set closed flag, rescue control point and correct last double point |
| closeWithGeometryChange(aRetval); |
| } |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| // makes the given indexed point the new polygon start point. To do that, the points in the |
| // polygon will be rotated. This is only valid for closed polygons, for non-closed ones |
| // an assertion will be triggered |
| B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount) |
| { |
| OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)"); |
| B2DPolygon aRetval; |
| |
| for(sal_uInt32 a(0); a < nPointCount; a++) |
| { |
| const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount); |
| aRetval.append(rCandidate.getB2DPoint(nSourceIndex)); |
| |
| if(rCandidate.areControlPointsUsed()) |
| { |
| aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex)); |
| aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex)); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| return rCandidate; |
| } |
| |
| B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd) |
| { |
| B2DPolygon aRetval; |
| |
| if(fLength < 0.0) |
| { |
| fLength = 0.0; |
| } |
| |
| if(!fTools::equalZero(fLength)) |
| { |
| if(fStart < 0.0) |
| { |
| fStart = 0.0; |
| } |
| |
| if(fEnd < 0.0) |
| { |
| fEnd = 0.0; |
| } |
| |
| if(fEnd < fStart) |
| { |
| fEnd = fStart; |
| } |
| |
| // iterate and consume pieces with fLength. First subdivide to reduce input to line segments |
| const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| |
| if(nPointCount > 1) |
| { |
| const bool bEndActive(!fTools::equalZero(fEnd)); |
| const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1); |
| B2DPoint aCurrent(aCandidate.getB2DPoint(0)); |
| double fPositionInEdge(fStart); |
| double fAbsolutePosition(fStart); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); |
| const B2DVector aEdge(aNext - aCurrent); |
| double fEdgeLength(aEdge.getLength()); |
| |
| if(!fTools::equalZero(fEdgeLength)) |
| { |
| while(fTools::less(fPositionInEdge, fEdgeLength)) |
| { |
| // move position on edge forward as long as on edge |
| const double fScalar(fPositionInEdge / fEdgeLength); |
| aRetval.append(aCurrent + (aEdge * fScalar)); |
| fPositionInEdge += fLength; |
| |
| if(bEndActive) |
| { |
| fAbsolutePosition += fLength; |
| |
| if(fTools::more(fAbsolutePosition, fEnd)) |
| { |
| break; |
| } |
| } |
| } |
| |
| // substract length of current edge |
| fPositionInEdge -= fEdgeLength; |
| } |
| |
| if(bEndActive && fTools::more(fAbsolutePosition, fEnd)) |
| { |
| break; |
| } |
| |
| // prepare next step |
| aCurrent = aNext; |
| } |
| |
| // keep closed state |
| aRetval.setClosed(aCandidate.isClosed()); |
| } |
| else |
| { |
| // source polygon has only one point, return unchanged |
| aRetval = aCandidate; |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight) |
| { |
| B2DPolygon aRetval; |
| |
| if(fWaveWidth < 0.0) |
| { |
| fWaveWidth = 0.0; |
| } |
| |
| if(fWaveHeight < 0.0) |
| { |
| fWaveHeight = 0.0; |
| } |
| |
| const bool bHasWidth(!fTools::equalZero(fWaveWidth)); |
| const bool bHasHeight(!fTools::equalZero(fWaveHeight)); |
| |
| if(bHasWidth) |
| { |
| if(bHasHeight) |
| { |
| // width and height, create waveline. First subdivide to reduce input to line segments |
| // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it |
| // may be added here again using the original last point from rCandidate. It may |
| // also be the case that rCandidate was closed. To simplify things it is handled here |
| // as if it was opened. |
| // Result from createEdgesOfGivenLength contains no curved segments, handle as straight |
| // edges. |
| const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth)); |
| const sal_uInt32 nPointCount(aEqualLenghEdges.count()); |
| |
| if(nPointCount > 1) |
| { |
| // iterate over straight edges, add start point |
| B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0)); |
| aRetval.append(aCurrent); |
| |
| for(sal_uInt32 a(0); a < nPointCount - 1; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex)); |
| const B2DVector aEdge(aNext - aCurrent); |
| const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge)); |
| const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight)); |
| |
| // add curve segment |
| aRetval.appendBezierSegment( |
| aCurrent + aControlOffset, |
| aNext - aControlOffset, |
| aNext); |
| |
| // prepare next step |
| aCurrent = aNext; |
| } |
| } |
| } |
| else |
| { |
| // width but no height -> return original polygon |
| aRetval = rCandidate; |
| } |
| } |
| else |
| { |
| // no width -> no waveline, stay empty and return |
| } |
| |
| return aRetval; |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // comparators with tolerance for 2D Polygons |
| |
| bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, const double& rfSmallValue) |
| { |
| const sal_uInt32 nPointCount(rCandidateA.count()); |
| |
| if(nPointCount != rCandidateB.count()) |
| return false; |
| |
| const bool bClosed(rCandidateA.isClosed()); |
| |
| if(bClosed != rCandidateB.isClosed()) |
| return false; |
| |
| const bool bAreControlPointsUsed(rCandidateA.areControlPointsUsed()); |
| |
| if(bAreControlPointsUsed != rCandidateB.areControlPointsUsed()) |
| return false; |
| |
| for(sal_uInt32 a(0); a < nPointCount; a++) |
| { |
| const B2DPoint aPoint(rCandidateA.getB2DPoint(a)); |
| |
| if(!aPoint.equal(rCandidateB.getB2DPoint(a), rfSmallValue)) |
| return false; |
| |
| if(bAreControlPointsUsed) |
| { |
| const basegfx::B2DPoint aPrev(rCandidateA.getPrevControlPoint(a)); |
| |
| if(!aPrev.equal(rCandidateB.getPrevControlPoint(a), rfSmallValue)) |
| return false; |
| |
| const basegfx::B2DPoint aNext(rCandidateA.getNextControlPoint(a)); |
| |
| if(!aNext.equal(rCandidateB.getNextControlPoint(a), rfSmallValue)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB) |
| { |
| const double fSmallValue(fTools::getSmallValue()); |
| |
| return equal(rCandidateA, rCandidateB, fSmallValue); |
| } |
| |
| // snap points of horizontal or vertical edges to discrete values |
| B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate) |
| { |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount > 1) |
| { |
| // Start by copying the source polygon to get a writeable copy. The closed state is |
| // copied by aRetval's initialisation, too, so no need to copy it in this method |
| B2DPolygon aRetval(rCandidate); |
| |
| // prepare geometry data. Get rounded from original |
| B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1))); |
| B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); |
| B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); |
| |
| // loop over all points. This will also snap the implicit closing edge |
| // even when not closed, but that's no problem here |
| for(sal_uInt32 a(0); a < nPointCount; a++) |
| { |
| // get next point. Get rounded from original |
| const bool bLastRun(a + 1 == nPointCount); |
| const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1); |
| const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); |
| const B2ITuple aNextTuple(basegfx::fround(aNextPoint)); |
| |
| // get the states |
| const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); |
| const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); |
| const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); |
| const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); |
| const bool bSnapX(bPrevVertical || bNextVertical); |
| const bool bSnapY(bPrevHorizontal || bNextHorizontal); |
| |
| if(bSnapX || bSnapY) |
| { |
| const B2DPoint aSnappedPoint( |
| bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), |
| bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); |
| |
| aRetval.setB2DPoint(a, aSnappedPoint); |
| } |
| |
| // prepare next point |
| if(!bLastRun) |
| { |
| aPrevTuple = aCurrTuple; |
| aCurrPoint = aNextPoint; |
| aCurrTuple = aNextTuple; |
| } |
| } |
| |
| return aRetval; |
| } |
| else |
| { |
| return rCandidate; |
| } |
| } |
| |
| bool containsOnlyHorizontalAndVerticalEdges(const B2DPolygon& rCandidate) |
| { |
| if(rCandidate.areControlPointsUsed()) |
| { |
| return false; |
| } |
| |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| |
| if(nPointCount < 2) |
| { |
| return true; |
| } |
| |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount); |
| basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(1); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex(a % nPointCount); |
| const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex)); |
| |
| if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY())) |
| { |
| return false; |
| } |
| |
| aLast = aCurrent; |
| } |
| |
| return true; |
| } |
| |
| B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| B2DVector aRetval(0.0, 0.0); |
| const sal_uInt32 nCount(rCandidate.count()); |
| |
| if(nIndex >= nCount) |
| { |
| // out of range |
| return aRetval; |
| } |
| |
| // start immediately at prev point compared to nIndex |
| const bool bClosed(rCandidate.isClosed()); |
| sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex); |
| |
| if(nPrev == nIndex) |
| { |
| // no previous, done |
| return aRetval; |
| } |
| |
| B2DCubicBezier aSegment; |
| |
| // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed, |
| // until zero. Use nIndex as stop criteria |
| while(nPrev != nIndex) |
| { |
| // get BezierSegment and tangent at the *end* of segment |
| rCandidate.getBezierSegment(nPrev, aSegment); |
| aRetval = aSegment.getTangent(1.0); |
| |
| if(!aRetval.equalZero()) |
| { |
| // if we have a tangent, return it |
| return aRetval; |
| } |
| |
| // prepare index before checked one |
| nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex; |
| } |
| |
| return aRetval; |
| } |
| |
| B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex) |
| { |
| B2DVector aRetval(0.0, 0.0); |
| const sal_uInt32 nCount(rCandidate.count()); |
| |
| if(nIndex >= nCount) |
| { |
| // out of range |
| return aRetval; |
| } |
| |
| // start at nIndex |
| const bool bClosed(rCandidate.isClosed()); |
| sal_uInt32 nCurrent(nIndex); |
| B2DCubicBezier aSegment; |
| |
| // go forward; if closed, do this until once around and back at start index (nIndex); if not |
| // closed, until last point (nCount - 1). Use nIndex as stop criteria |
| do |
| { |
| // get BezierSegment and tangent at the *beginning* of segment |
| rCandidate.getBezierSegment(nCurrent, aSegment); |
| aRetval = aSegment.getTangent(0.0); |
| |
| if(!aRetval.equalZero()) |
| { |
| // if we have a tangent, return it |
| return aRetval; |
| } |
| |
| // prepare next index |
| nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex; |
| } |
| while(nCurrent != nIndex); |
| |
| return aRetval; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // converters for com::sun::star::drawing::PointSequence |
| |
| B2DPolygon UnoPointSequenceToB2DPolygon( |
| const com::sun::star::drawing::PointSequence& rPointSequenceSource, |
| bool bCheckClosed) |
| { |
| B2DPolygon aRetval; |
| const sal_uInt32 nLength(rPointSequenceSource.getLength()); |
| |
| if(nLength) |
| { |
| aRetval.reserve(nLength); |
| const com::sun::star::awt::Point* pArray = rPointSequenceSource.getConstArray(); |
| const com::sun::star::awt::Point* pArrayEnd = pArray + rPointSequenceSource.getLength(); |
| |
| for(;pArray != pArrayEnd; pArray++) |
| { |
| aRetval.append(B2DPoint(pArray->X, pArray->Y)); |
| } |
| |
| if(bCheckClosed) |
| { |
| // check for closed state flag |
| tools::checkClosed(aRetval); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| void B2DPolygonToUnoPointSequence( |
| const B2DPolygon& rPolygon, |
| com::sun::star::drawing::PointSequence& rPointSequenceRetval) |
| { |
| B2DPolygon aPolygon(rPolygon); |
| |
| if(aPolygon.areControlPointsUsed()) |
| { |
| OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)"); |
| aPolygon = aPolygon.getDefaultAdaptiveSubdivision(); |
| } |
| |
| const sal_uInt32 nPointCount(aPolygon.count()); |
| |
| if(nPointCount) |
| { |
| // Take closed state into account, the API polygon still uses the old closed definition |
| // with last/first point are identical (cannot hold information about open polygons with identical |
| // first and last point, though) |
| const bool bIsClosed(aPolygon.isClosed()); |
| |
| rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount); |
| com::sun::star::awt::Point* pSequence = rPointSequenceRetval.getArray(); |
| |
| for(sal_uInt32 b(0); b < nPointCount; b++) |
| { |
| const B2DPoint aPoint(aPolygon.getB2DPoint(b)); |
| const com::sun::star::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY())); |
| |
| *pSequence = aAPIPoint; |
| pSequence++; |
| } |
| |
| // copy first point if closed |
| if(bIsClosed) |
| { |
| *pSequence = *rPointSequenceRetval.getArray(); |
| } |
| } |
| else |
| { |
| rPointSequenceRetval.realloc(0); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // converters for com::sun::star::drawing::PointSequence and |
| // com::sun::star::drawing::FlagSequence to B2DPolygon (curved polygons) |
| |
| B2DPolygon UnoPolygonBezierCoordsToB2DPolygon( |
| const com::sun::star::drawing::PointSequence& rPointSequenceSource, |
| const com::sun::star::drawing::FlagSequence& rFlagSequenceSource, |
| bool bCheckClosed) |
| { |
| const sal_uInt32 nCount((sal_uInt32)rPointSequenceSource.getLength()); |
| OSL_ENSURE(nCount == (sal_uInt32)rFlagSequenceSource.getLength(), |
| "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)"); |
| |
| // prepare new polygon |
| B2DPolygon aRetval; |
| const com::sun::star::awt::Point* pPointSequence = rPointSequenceSource.getConstArray(); |
| const com::sun::star::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray(); |
| |
| // get first point and flag |
| B2DPoint aNewCoordinatePair(pPointSequence->X, pPointSequence->Y); pPointSequence++; |
| com::sun::star::drawing::PolygonFlags ePolygonFlag(*pFlagSequence); pFlagSequence++; |
| B2DPoint aControlA; |
| B2DPoint aControlB; |
| |
| // first point is not allowed to be a control point |
| OSL_ENSURE(com::sun::star::drawing::PolygonFlags_CONTROL != ePolygonFlag, |
| "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)"); |
| |
| // add first point as start point |
| aRetval.append(aNewCoordinatePair); |
| |
| for(sal_uInt32 b(1); b < nCount;) |
| { |
| // prepare loop |
| bool bControlA(false); |
| bool bControlB(false); |
| |
| // get next point and flag |
| aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y); |
| ePolygonFlag = *pFlagSequence; |
| pPointSequence++; pFlagSequence++; b++; |
| |
| if(b < nCount && com::sun::star::drawing::PolygonFlags_CONTROL == ePolygonFlag) |
| { |
| aControlA = aNewCoordinatePair; |
| bControlA = true; |
| |
| // get next point and flag |
| aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y); |
| ePolygonFlag = *pFlagSequence; |
| pPointSequence++; pFlagSequence++; b++; |
| } |
| |
| if(b < nCount && com::sun::star::drawing::PolygonFlags_CONTROL == ePolygonFlag) |
| { |
| aControlB = aNewCoordinatePair; |
| bControlB = true; |
| |
| // get next point and flag |
| aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y); |
| ePolygonFlag = *pFlagSequence; |
| pPointSequence++; pFlagSequence++; b++; |
| } |
| |
| // two or no control points are consumed, another one would be an error. |
| // It's also an error if only one control point was read |
| OSL_ENSURE(com::sun::star::drawing::PolygonFlags_CONTROL != ePolygonFlag && bControlA == bControlB, |
| "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"); |
| |
| // the previous writes used the B2DPolyPoygon -> PolyPolygon converter |
| // which did not create minimal PolyPolygons, but created all control points |
| // as null vectors (identical points). Because of the former P(CA)(CB)-norm of |
| // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being |
| // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new |
| // export format can be read without errors by the old OOo-versions, so we need only |
| // to correct here at read and do not need to export a wrong but compatible version |
| // for the future. |
| if(bControlA |
| && aControlA.equal(aControlB) |
| && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1))) |
| { |
| bControlA = bControlB = false; |
| } |
| |
| if(bControlA) |
| { |
| // add bezier edge |
| aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair); |
| } |
| else |
| { |
| // add edge |
| aRetval.append(aNewCoordinatePair); |
| } |
| } |
| |
| // #i72807# API import uses old line start/end-equal definition for closed, |
| // so we need to correct this to closed state here |
| if(bCheckClosed) |
| { |
| checkClosed(aRetval); |
| } |
| |
| return aRetval; |
| } |
| |
| void B2DPolygonToUnoPolygonBezierCoords( |
| const B2DPolygon& rPolygon, |
| com::sun::star::drawing::PointSequence& rPointSequenceRetval, |
| com::sun::star::drawing::FlagSequence& rFlagSequenceRetval) |
| { |
| const sal_uInt32 nPointCount(rPolygon.count()); |
| |
| if(nPointCount) |
| { |
| const bool bCurve(rPolygon.areControlPointsUsed()); |
| const bool bClosed(rPolygon.isClosed()); |
| |
| if(nPointCount) |
| { |
| if(bCurve) |
| { |
| // calculate target point count |
| const sal_uInt32 nLoopCount(bClosed ? nPointCount : (nPointCount ? nPointCount - 1 : 0)); |
| |
| if(nLoopCount) |
| { |
| // prepare target data. The real needed number of target points (and flags) |
| // could only be calculated by using two loops, so use dynamic memory |
| std::vector< com::sun::star::awt::Point > aCollectPoints; |
| std::vector< com::sun::star::drawing::PolygonFlags > aCollectFlags; |
| |
| // reserve maximum creatable points |
| const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1); |
| aCollectPoints.reserve(nMaxTargetCount); |
| aCollectFlags.reserve(nMaxTargetCount); |
| |
| // prepare current bezier segment by setting start point |
| B2DCubicBezier aBezierSegment; |
| aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(0); a < nLoopCount; a++) |
| { |
| // add current point (always) and remember StartPointIndex for evtl. later corrections |
| const sal_uInt32 nStartPointIndex(aCollectPoints.size()); |
| aCollectPoints.push_back( |
| com::sun::star::awt::Point( |
| fround(aBezierSegment.getStartPoint().getX()), |
| fround(aBezierSegment.getStartPoint().getY()))); |
| aCollectFlags.push_back(com::sun::star::drawing::PolygonFlags_NORMAL); |
| |
| // prepare next segment |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex)); |
| aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a)); |
| aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex)); |
| |
| if(aBezierSegment.isBezier()) |
| { |
| // if bezier is used, add always two control points due to the old schema |
| aCollectPoints.push_back( |
| com::sun::star::awt::Point( |
| fround(aBezierSegment.getControlPointA().getX()), |
| fround(aBezierSegment.getControlPointA().getY()))); |
| aCollectFlags.push_back(com::sun::star::drawing::PolygonFlags_CONTROL); |
| |
| aCollectPoints.push_back( |
| com::sun::star::awt::Point( |
| fround(aBezierSegment.getControlPointB().getX()), |
| fround(aBezierSegment.getControlPointB().getY()))); |
| aCollectFlags.push_back(com::sun::star::drawing::PolygonFlags_CONTROL); |
| } |
| |
| // test continuity with previous control point to set flag value |
| if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a)) |
| { |
| const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a)); |
| |
| if(CONTINUITY_C1 == eCont) |
| { |
| aCollectFlags[nStartPointIndex] = com::sun::star::drawing::PolygonFlags_SMOOTH; |
| } |
| else if(CONTINUITY_C2 == eCont) |
| { |
| aCollectFlags[nStartPointIndex] = com::sun::star::drawing::PolygonFlags_SYMMETRIC; |
| } |
| } |
| |
| // prepare next loop |
| aBezierSegment.setStartPoint(aBezierSegment.getEndPoint()); |
| } |
| |
| if(bClosed) |
| { |
| // add first point again as closing point due to old definition |
| aCollectPoints.push_back(aCollectPoints[0]); |
| aCollectFlags.push_back(com::sun::star::drawing::PolygonFlags_NORMAL); |
| } |
| else |
| { |
| // add last point as closing point |
| const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1L)); |
| aCollectPoints.push_back( |
| com::sun::star::awt::Point( |
| fround(aClosingPoint.getX()), |
| fround(aClosingPoint.getY()))); |
| aCollectFlags.push_back(com::sun::star::drawing::PolygonFlags_NORMAL); |
| } |
| |
| // copy collected data to target arrays |
| const sal_uInt32 nTargetCount(aCollectPoints.size()); |
| OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)"); |
| |
| rPointSequenceRetval.realloc((sal_Int32)nTargetCount); |
| rFlagSequenceRetval.realloc((sal_Int32)nTargetCount); |
| com::sun::star::awt::Point* pPointSequence = rPointSequenceRetval.getArray(); |
| com::sun::star::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray(); |
| |
| for(sal_uInt32 a(0); a < nTargetCount; a++) |
| { |
| *pPointSequence = aCollectPoints[a]; |
| *pFlagSequence = aCollectFlags[a]; |
| pPointSequence++; |
| pFlagSequence++; |
| } |
| } |
| } |
| else |
| { |
| // straightforward point list creation |
| const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0)); |
| |
| rPointSequenceRetval.realloc((sal_Int32)nTargetCount); |
| rFlagSequenceRetval.realloc((sal_Int32)nTargetCount); |
| |
| com::sun::star::awt::Point* pPointSequence = rPointSequenceRetval.getArray(); |
| com::sun::star::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray(); |
| |
| for(sal_uInt32 a(0); a < nPointCount; a++) |
| { |
| const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a)); |
| const com::sun::star::awt::Point aAPIPoint( |
| fround(aB2DPoint.getX()), |
| fround(aB2DPoint.getY())); |
| |
| *pPointSequence = aAPIPoint; |
| *pFlagSequence = com::sun::star::drawing::PolygonFlags_NORMAL; |
| pPointSequence++; |
| pFlagSequence++; |
| } |
| |
| if(bClosed) |
| { |
| // add first point as closing point |
| *pPointSequence = *rPointSequenceRetval.getConstArray(); |
| *pFlagSequence = com::sun::star::drawing::PolygonFlags_NORMAL; |
| } |
| } |
| } |
| } |
| else |
| { |
| rPointSequenceRetval.realloc(0); |
| rFlagSequenceRetval.realloc(0); |
| } |
| } |
| |
| } // end of namespace tools |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // eof |