/* -*- 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.
--> --------------------
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.