tdf#130655 added callback interface to ::applyLineDashing

This version of the tooling method allows to avoid collecting line
snippets in a return value PolyPolygon. Instead, offer lambda
functions to get callbacks for created snippets. The original
method using a B2DPolyPolygon return value is adapted to already
use this, so serves as example of usage and ensures that only
one identical algorithm is used.

Change-Id: Ie306968a895ad280fc2425fb40b3244769216ba0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/88684
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
This commit is contained in:
Armin Le Grand (Collabora) 2020-02-14 12:32:42 +01:00 committed by Armin Le Grand
parent 75500a4161
commit 0dc4fddb9c
2 changed files with 273 additions and 190 deletions

View File

@ -1110,7 +1110,102 @@ namespace basegfx::utils
return false;
}
void applyLineDashing(const B2DPolygon& rCandidate, const std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fDotDashLength)
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();
}
// provide callbacks as lambdas
auto aLineCallback(
nullptr == pLineTarget
? std::function<void(const basegfx::B2DPolygon&)>()
: [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); });
auto aGapCallback(
nullptr == pGapTarget
? std::function<void(const basegfx::B2DPolygon&)>()
: [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); });
// call version that uses callbacks
applyLineDashing(
rCandidate,
rDotDashArray,
aLineCallback,
aGapCallback,
fDotDashLength);
}
static void implHandleSnippet(
const B2DPolygon& rSnippet,
std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
B2DPolygon& rFirst,
B2DPolygon& rLast)
{
if(rSnippet.isClosed())
{
if(!rFirst.count())
{
rFirst = rSnippet;
}
else
{
if(rLast.count())
{
rTargetCallback(rLast);
}
rLast = rSnippet;
}
}
else
{
rTargetCallback(rSnippet);
}
}
static void implHandleFirstLast(
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(rLast.count())
{
rTargetCallback(rLast);
}
if(rFirst.count())
{
rTargetCallback(rFirst);
}
}
void applyLineDashing(
const B2DPolygon& rCandidate,
const std::vector<double>& rDotDashArray,
std::function<void(const basegfx::B2DPolygon& rSnippet)> aLineTargetCallback,
std::function<void(const basegfx::B2DPolygon& rSnippet)> aGapTargetCallback,
double fDotDashLength)
{
const sal_uInt32 nPointCount(rCandidate.count());
const sal_uInt32 nDotDashCount(rDotDashArray.size());
@ -1120,154 +1215,133 @@ namespace basegfx::utils
fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
}
if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount)
if(fTools::lessOrEqual(fDotDashLength, 0.0) || (!aLineTargetCallback && !aGapTargetCallback) || !nPointCount)
{
// clear targets
if(pLineTarget)
// parameters make no sense, just add source to targets
if(aLineTargetCallback)
{
pLineTarget->clear();
aLineTargetCallback(rCandidate);
}
if(pGapTarget)
if(aGapTargetCallback)
{
pGapTarget->clear();
aGapTargetCallback(rCandidate);
}
// prepare current edge's start
B2DCubicBezier aCurrentEdge;
const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
return;
}
// prepare DotDashArray iteration and the line/gap switching bool
sal_uInt32 nDotDashIndex(0);
bool bIsLine(true);
double fDotDashMovingLength(rDotDashArray[0]);
B2DPolygon aSnippet;
// prepare current edge's start
B2DCubicBezier aCurrentEdge;
const bool bIsClosed(rCandidate.isClosed());
const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
// iterate over all edges
for(sal_uInt32 a(0); a < nEdgeCount; a++)
// prepare DotDashArray iteration and the line/gap switching bool
sal_uInt32 nDotDashIndex(0);
bool bIsLine(true);
double fDotDashMovingLength(rDotDashArray[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();
if(aCurrentEdge.isBezier())
{
// 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));
// bezier segment
const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
const double fEdgeLength(aCubicBezierHelper.getLength());
// check if we have a trivial bezier segment -> possible fallback to edge
aCurrentEdge.testAndSolveTrivialBezier();
if(aCurrentEdge.isBezier())
if(!fTools::equalZero(fEdgeLength))
{
// bezier segment
const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
const double fEdgeLength(aCubicBezierHelper.getLength());
if(!fTools::equalZero(fEdgeLength))
while(fTools::less(fDotDashMovingLength, 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);
// new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
B2DCubicBezier aRight;
const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
if(!aSnippet.count())
{
aSnippet.append(aRight.getStartPoint());
aSnippet.append(aBezierSnippet.getStartPoint());
}
aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
}
aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.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(bHandleLine)
{
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();
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
// prepare next DotDashArray step and flip line/gap flag
fLastDotDashMovingLength = fDotDashMovingLength;
fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
bIsLine = !bIsLine;
if(bHandleGap)
{
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
aSnippet.clear();
}
// append snippet [fLastDotDashMovingLength, fEdgeLength]
const bool bHandleLine(bIsLine && pLineTarget);
const bool bHandleGap(!bIsLine && pGapTarget);
// 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 && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
B2DCubicBezier aRight;
const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
aCurrentEdge.split(fBezierSplit, nullptr, &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 && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
@ -1276,88 +1350,75 @@ namespace basegfx::utils
aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
}
aSnippet.append(aCurrentEdge.getEndPoint());
aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
if(bHandleLine)
{
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
if(bHandleGap)
{
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
aSnippet.clear();
}
// prepare move to next edge
fDotDashMovingLength -= fEdgeLength;
// prepare next DotDashArray step and flip line/gap flag
fLastDotDashMovingLength = fDotDashMovingLength;
fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
bIsLine = !bIsLine;
}
}
// prepare next edge step (end point gets new start point)
aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
}
// append snippet [fLastDotDashMovingLength, fEdgeLength]
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
// 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 direct 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)))
if(bHandleLine || bHandleGap)
{
// 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(!aSnippet.count())
{
aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
}
aSnippet.append(aCurrentEdge.getEndPoint());
}
// prepare move to next edge
fDotDashMovingLength -= fEdgeLength;
}
}
if(pGapTarget)
// prepare next edge step (end point gets new start point)
aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
}
// append last intermediate results (if exists)
if(aSnippet.count())
{
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine)
{
const sal_uInt32 nCount(pGapTarget->count());
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
if(nCount > 1)
{
// these polygons were created above, there exists none with less than two points,
// thus direct 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);
}
}
if(bHandleGap)
{
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
}
else
{
// parameters make no sense, just add source to targets
if(pLineTarget)
{
pLineTarget->append(rCandidate);
}
if(pGapTarget)
{
pGapTarget->append(rCandidate);
}
if(bIsClosed && aLineTargetCallback)
{
implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine);
}
if(bIsClosed && aGapTargetCallback)
{
implHandleFirstLast(aGapTargetCallback, aFirstGap, aLastGap);
}
}

View File

@ -20,6 +20,9 @@
#ifndef INCLUDED_BASEGFX_POLYGON_B2DPOLYGONTOOLS_HXX
#define INCLUDED_BASEGFX_POLYGON_B2DPOLYGONTOOLS_HXX
#include <vector>
#include <functional>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <basegfx/range/b2drectangle.hxx>
@ -27,7 +30,6 @@
#include <basegfx/polygon/b2dpolygontriangulator.hxx>
#include <com/sun/star/drawing/PointSequence.hpp>
#include <com/sun/star/drawing/FlagSequence.hpp>
#include <vector>
#include <basegfx/basegfxdllapi.h>
#include <o3tl/typed_flags_set.hxx>
@ -188,7 +190,27 @@ namespace basegfx
@param fFullDashDotLen
The summed-up length of the rDotDashArray. If zero, it will
be calculated internally.
There is now a 2nd version that allows to provide callback
functions that get called when a snippet of a line/gap is
produced and needs to be added. This allows to use it like
a 'pipeline'. When using this (e.g. the 1st version uses
this internally to guarantee the same algorithm is used)
it is not needed to accumulate a potentially huge number
of polygons in the result-polyPolygons, but e.g. consume
them directly in the caller. Example is renderinmg a
dashed line but without creating the potentially huge amount
of polygons.
The 2nd version will also merge first/last line/gap snippets
if the input polygon is closed and the start/end-points match
accordingly - at the cost that this will be delivered last.
*/
BASEGFX_DLLPUBLIC void applyLineDashing(
const B2DPolygon& rCandidate,
const std::vector<double>& rDotDashArray,
std::function<void(const basegfx::B2DPolygon& rSnippet)> aLineTargetCallback,
std::function<void(const basegfx::B2DPolygon& rSnippet)> aGapTargetCallback = std::function<void(const basegfx::B2DPolygon&)>(),
double fDotDashLength = 0.0);
BASEGFX_DLLPUBLIC void applyLineDashing(
const B2DPolygon& rCandidate,
const ::std::vector<double>& rDotDashArray,