/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 .
*/ #include <numeric> #include <algorithm> #include <stack>
// 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 (!)");
sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
{
OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
// perf: try to avoid too many reallocations by guessing the result's pointcount
aRetval.reserve(nPointCount*4);
// add start point (always)
aRetval.append(aBezier.getStartPoint());
for(sal_uInt32 a(0); 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(fDistanceBound == 0.0)
{ // If not set, use B2DCubicBezier functionality to guess a rough value constdouble 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;
}
// perf: try to avoid too many reallocations 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(fAngleBound == 0.0)
{
fAngleBound = ANGLE_BOUND_START_VALUE;
} elseif(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
{
fAngleBound = 0.1;
}
for(sal_uInt32 a(0); 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();
// cross-over in Y? tdf#130150 use full precision, no need for epsilon constbool bCompYA(aPreviousPoint.getY() > rPoint.getY()); constbool bCompYB(aCurrentPoint.getY() > rPoint.getY());
if(bCompYA != bCompYB)
{ // cross-over in X? tdf#130150 use full precision, no need for epsilon constbool bCompXA(aPreviousPoint.getX() > rPoint.getX()); constbool bCompXB(aCurrentPoint.getX() > rPoint.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;
}
}
if( nPointCount == 1 )
{ // only one point (i.e. no edge) - simply take that point
aRetval = rCandidate.getB2DPoint(0);
} elseif(nPointCount > 1)
{ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
sal_uInt32 nIndex(0); bool bIndexDone(false);
// get length if not given if(fTools::equalZero(fLength))
{
fLength = getLength(rCandidate);
}
if (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 + 1) * fLength;
} else
{ // crop to polygon start
fDistance = 0.0;
bIndexDone = true;
}
} elseif(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 -= static_cast<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+1 < 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 chosen edge const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
aRetval = rCandidate.getB2DPoint(nNextIndex);
} elseif(fTools::equalZero(fDistance))
{ // start point of chosen edge
} 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); constdouble fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
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);
}
if(aBezierSegment.isBezier())
{ // use B2DCubicBezierHelper to bridge the non-linear gap between // length and bezier distances const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); constdouble fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
B2DCubicBezier aRight;
if(aBezierSegment.isBezier())
{ // use B2DCubicBezierHelper to bridge the non-linear gap between // length and bezier distances const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); constdouble fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
B2DCubicBezier aLeft;
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;
}
returntrue;
}
}
}
returnfalse;
}
void applyLineDashing( const B2DPolygon& rCandidate, const std::vector<double>& rDotDashArray,
B2DPolyPolygon* pLineTarget,
B2DPolyPolygon* pGapTarget, double fDotDashLength)
{ // clear targets in any case if(pLineTarget)
{
pLineTarget->clear();
}
if(pGapTarget)
{
pGapTarget->clear();
}
// call version that uses callbacks
applyLineDashing(
rCandidate,
rDotDashArray, // provide callbacks as lambdas
(!pLineTarget
? std::function<void(const basegfx::B2DPolygon&)>()
: [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); }),
(!pGapTarget
? std::function<void(const basegfx::B2DPolygon&)>()
: [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); }),
fDotDashLength);
}
staticvoid implHandleFirstLast( const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
B2DPolygon& rFirst,
B2DPolygon& rLast)
{ if(rFirst.count() && rLast.count()
&& rFirst.getB2DPoint(0).equal(rLast.getB2DPoint(rLast.count() - 1)))
{ // start of first and end of last are the same -> merge them
rLast.append(rFirst);
rLast.removeDoublePoints();
rFirst.clear();
}
if(fDotDashLength <= 0.0 || (!rLineTargetCallback && !rGapTargetCallback) || !nPointCount)
{ // parameters make no sense, just add source to targets if (rLineTargetCallback)
rLineTargetCallback(rCandidate);
if (rGapTargetCallback)
rGapTargetCallback(rCandidate);
return;
}
// precalculate maximal acceptable length of candidate polygon assuming // we want to create a maximum of fNumberOfAllowedSnippets. For // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap. staticconstdouble fNumberOfAllowedSnippets(65535.0 * 2.0); constdouble fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size())); constdouble fCandidateLength(basegfx::utils::getLength(rCandidate));
std::vector<double> aDotDashArray(rDotDashArray);
if(fCandidateLength > fAllowedLength)
{ // we would produce more than fNumberOfAllowedSnippets, so // adapt aDotDashArray to exactly produce assumed number. Also // assert this to let the caller know about it. // If this asserts: Please think about checking your DotDashArray // before calling this function or evtl. use the callback version // to *not* produce that much of data. Even then, you may still // think about producing too much runtime (!)
assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
// calculate correcting factor, apply to aDotDashArray and fDotDashLength // to enlarge these as needed constdouble fFactor(fCandidateLength / fAllowedLength);
std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
}
// prepare DotDashArray iteration and the line/gap switching bool
sal_uInt32 nDotDashIndex(0); bool bIsLine(true); double fDotDashMovingLength(aDotDashArray[0]);
B2DPolygon aSnippet;
// remember 1st and last snippets to try to merge after execution // is complete and hand to callback
B2DPolygon aFirstLine, aLastLine;
B2DPolygon aFirstGap, aLastGap;
// 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();
// 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())); constdouble fZero(0.0); constdouble fOne(1.0);
if(fTools::less(fCut, fZero))
{ // left of rEdgeStart
bDoDistanceTestStart = true;
} elseif(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); constdouble fDistanceSquare(aDelta.scalar(aDelta));
// 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());
// 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))
{ returntrue;
}
}
returnfalse;
}
// Calculates distance of curve point to its control point for a Bézier curve, that // approximates a unit circle arc. fAngle is the center angle of the circle arc. The // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425. staticdouble impDistanceBezierPointToControl(double fAngle)
{
SAL_WARN_IF(fAngle < 0 || fAngle > M_PI_2,"basegfx","angle not suitable for approximate circle"); if (0 <= fAngle && fAngle <= M_PI_2)
{ return 4.0/3.0 * ( tan(fAngle/4.0));
} else return 0;
}
if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
{ // 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());
B2DPolygon aPolygon {
aBottomCenter,
{ rRect.getMinX(), rRect.getMaxY() },
{ rRect.getMinX(), rRect.getMinY() },
{ rRect.getMaxX(), rRect.getMinY() },
{ rRect.getMaxX(), rRect.getMaxY() }
};
// close
aPolygon.setClosed( true );
return aPolygon;
} elseif(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
{ // in both directions full radius, use ellipse const B2DPoint aCenter(rRect.getCenter()); constdouble fRectRadiusX(rRect.getWidth() / 2.0); constdouble fRectRadiusY(rRect.getHeight() / 2.0);
// remove double created points if there are extreme radii involved if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
{
aRetval.removeDoublePoints();
}
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)); constdouble fFactor(impDistanceBezierPointToControl(fEnd - fStart));
while(nSegment != nEndSegment)
{ // No end in this sector, add full sector.
fSegEndRad = (nSegment + 1) * fAnglePerSegment;
aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
if(aOrientation == B2VectorOrientation::Neutral)
{ // current has neutral orientation, leave it out and prepare next
aCurrPoint = aNextPoint;
} else
{ // add current point
aRetval.append(aCurrPoint);
// tdf#88352 increase numerical correctness and use rtl::math::approxEqual // instead of fTools::equalZero which compares with a fixed small value if(fCrossA == 0.0)
{ // one point on the line return bWithLine;
}
namespace
{ /// return 0 for input of 0, -1 for negative and 1 for positive input int lcl_sgn( constdouble n )
{ return n == 0.0 ? 0 : 1 - 2*int(std::signbit(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() )
{ returnfalse;
}
// 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 ) returnfalse; // oblique edge - for sure no rect
// 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 constint 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 ) returnfalse;
}
++nNumTurns;
// More than four 90 degree turns are an // indication that this must not be a rectangle. if( nNumTurns > 4 ) returnfalse;
}
// store current state for the next turn
nVerticalEdgeType = nCurrVerticalEdgeType;
nHorizontalEdgeType = nCurrHorizontalEdgeType;
bNullVertex = false; // won't reach this line, // if bCurrNullVertex is // true - see above
}
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(rtl::math::approxEqual(1.0, rCut))
{ // correct rEdgeIndex when not last point if(rCandidate.isClosed())
{
rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
rCut = 0.0;
} else
{ if(rEdgeIndex != nEdgeCount - 1)
{
rEdgeIndex++;
rCut = 0.0;
}
}
}
}
bRetval = true;
}
} break;
} case B2VectorContinuity::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); constdouble fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
aVectorPrev.normalize();
aVectorNext.normalize(); const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
if(aOrientation == B2VectorOrientation::Neutral && 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);
// try to avoid costly reallocations
aRetval.reserve( nEdgeCount+1);
// add start point
aRetval.append(aBezier.getStartPoint());
for(sal_uInt32 a(0); 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());
// 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(!fTools::equalZero(fEdgeLength))
{ while(fTools::less(fPositionInEdge, fEdgeLength))
{ // move position on edge forward as long as on edge constdouble fScalar(fPositionInEdge / fEdgeLength);
aRetval.append(aCurrent + (aEdge * fScalar));
fPositionInEdge += fLength;
// keep closed state
aRetval.setClosed(aCandidate.isClosed());
} else
{ // source polygon has only one point, return unchanged
aRetval = aCandidate;
}
}
if(bHasWidth)
{ constbool bHasHeight(!fTools::equalZero(fWaveHeight)); 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);
// 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;
}
// 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 constbool 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 constbool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); constbool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); constbool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); constbool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); constbool bSnapX(bPrevVertical || bNextVertical); constbool bSnapY(bPrevHorizontal || bNextHorizontal);
// 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;
}
if(nIndex >= nCount)
{ // out of range return aRetval;
}
// start at nIndex constbool 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;
}
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) constbool bIsClosed(aPolygon.isClosed());
// get first point and flag
B2DPoint aNewCoordinatePair(rPointSequenceSource[0].X, rPointSequenceSource[0].Y);
B2DPoint aControlA;
B2DPoint aControlB;
// first point is not allowed to be a control point
OSL_ENSURE(rFlagSequenceSource[0] != css::drawing::PolygonFlags_CONTROL, "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
// add first point as start point
aRetval.append(aNewCoordinatePair);
// get next point and flag
aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
css::drawing::PolygonFlags ePolygonFlag = rFlagSequenceSource[b];
b++;
// get next point and flag
aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
ePolygonFlag = rFlagSequenceSource[b];
b++;
}
// get next point and flag
aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
ePolygonFlag = rFlagSequenceSource[b];
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
SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB, "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
// the previous writes used the B2DPolyPolygon -> utils::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 = false;
}
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< css::awt::Point > aCollectPoints;
std::vector< css::drawing::PolygonFlags > aCollectFlags;
// 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.emplace_back(
fround(aBezierSegment.getStartPoint().getX()),
fround(aBezierSegment.getStartPoint().getY()));
aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
if(aBezierSegment.isBezier())
{ // if bezier is used, add always two control points due to the old schema
aCollectPoints.emplace_back(
fround(aBezierSegment.getControlPointA().getX()),
fround(aBezierSegment.getControlPointA().getY()));
aCollectFlags.push_back(css::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));
// 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(css::drawing::PolygonFlags_NORMAL);
} else
{ // add last point as closing point const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
aCollectPoints.emplace_back(
fround(aClosingPoint.getX()),
fround(aClosingPoint.getY()));
aCollectFlags.push_back(css::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 (!)");
// The solution does not use recursion directly, since this could lead to a stack overflow if // nPointCount is very large. Instead, an own stack is used. This does not contain points, but // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note // whether a point of rCandidate belongs to the output polygon or not.
std::vector<bool> bIsKeptVec(nPointCount, false);
bIsKeptVec[0] = true;
bIsKeptVec[nPointCount - 1] = true;
sal_uInt32 nKept = 2;
std::stack<std::pair<sal_uInt32, sal_uInt32>> aUnfinishedRangesStack;
// The RDP algorithm draws a straight line from the first point to the last point of a range. // Then, from the inner points of the range, the point that has the largest distance to the line // is determined. If the distance is greater than the tolerance, this point is kept and the lower // and upper sub-segments of the range are treated in the same way. If the distance is smaller // than the tolerance, none of the inner points will be kept.
sal_uInt32 nLowIndex = 0;
sal_uInt32 nHighIndex = nPointCount - 1; bool bContinue = true; do
{
bContinue = false; if (nHighIndex - nLowIndex < 2) // maximal two points, range is finished.
{ // continue with sibling upper range if any if (!aUnfinishedRangesStack.empty())
{
std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
aUnfinishedRangesStack.pop();
nLowIndex = aIndexPair.first;
nHighIndex = aIndexPair.second;
bContinue = true;
}
} else// the range needs examine the max distance
{ // Get maximal distance of inner points of the range to the straight line from start to // end point of the range. // For calculation of the distance we use the Hesse normal form of the straight line.
B2DPoint aLowPoint = rCandidate.getB2DPoint(nLowIndex);
B2DPoint aHighPoint = rCandidate.getB2DPoint(nHighIndex);
B2DVector aNormalVec
= basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint) - B2DVector(aLowPoint)); double fLineOriginDistance = aNormalVec.scalar(B2DVector(aLowPoint)); double fMaxDist = 0;
sal_uInt32 nMaxPointIndex = nLowIndex; for (sal_uInt32 i = nLowIndex + 1; i < nHighIndex; i++)
{ double fDistance
= aNormalVec.scalar(B2DVector(rCandidate.getB2DPoint(i))) - fLineOriginDistance; if (std::fabs(fDistance) > fMaxDist)
{
fMaxDist = std::fabs(fDistance);
nMaxPointIndex = i;
}
}
if (fMaxDist >= fTolerance)
{ // We need to divide the current range into two sub ranges.
bIsKeptVec[nMaxPointIndex] = true;
nKept++; // continue with lower sub range and push upper sub range to stack
aUnfinishedRangesStack.push(std::make_pair(nMaxPointIndex, nHighIndex));
nHighIndex = nMaxPointIndex;
bContinue = true;
} else
{ // We do not keep any of the inner points of the current range. // continue with sibling upper range if any if (!aUnfinishedRangesStack.empty())
{
std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
aUnfinishedRangesStack.pop();
nLowIndex = aIndexPair.first;
nHighIndex = aIndexPair.second;
bContinue = true;
}
}
}
} while (bContinue);
// Put all points which are marked as "keep" into the result polygon
B2DPolygon aResultPolygon;
aResultPolygon.reserve(nKept); for (sal_uInt32 i = 0; i < nPointCount; i++)
{ if (bIsKeptVec[i])
aResultPolygon.append(rCandidate.getB2DPoint(i));
} return aResultPolygon;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.