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,19 +1110,14 @@ 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)
{
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
// clear targets in any case
if(pLineTarget)
{
pLineTarget->clear();
@@ -1133,9 +1128,113 @@ namespace basegfx::utils
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());
if(fTools::lessOrEqual(fDotDashLength, 0.0))
{
fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
}
if(fTools::lessOrEqual(fDotDashLength, 0.0) || (!aLineTargetCallback && !aGapTargetCallback) || !nPointCount)
{
// parameters make no sense, just add source to targets
if(aLineTargetCallback)
{
aLineTargetCallback(rCandidate);
}
if(aGapTargetCallback)
{
aGapTargetCallback(rCandidate);
}
return;
}
// prepare current edge's start
B2DCubicBezier aCurrentEdge;
const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
const bool bIsClosed(rCandidate.isClosed());
const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
// prepare DotDashArray iteration and the line/gap switching bool
@@ -1144,6 +1243,11 @@ namespace basegfx::utils
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++)
{
@@ -1168,8 +1272,8 @@ namespace basegfx::utils
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);
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
@@ -1186,11 +1290,12 @@ namespace basegfx::utils
if(bHandleLine)
{
pLineTarget->append(aSnippet);
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
else
if(bHandleGap)
{
pGapTarget->append(aSnippet);
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
aSnippet.clear();
@@ -1203,8 +1308,8 @@ namespace basegfx::utils
}
// append closing snippet [fLastDotDashMovingLength, fEdgeLength]
const bool bHandleLine(bIsLine && pLineTarget);
const bool bHandleGap(!bIsLine && pGapTarget);
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
@@ -1235,8 +1340,8 @@ namespace basegfx::utils
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);
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
@@ -1249,11 +1354,12 @@ namespace basegfx::utils
if(bHandleLine)
{
pLineTarget->append(aSnippet);
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
else
if(bHandleGap)
{
pGapTarget->append(aSnippet);
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
aSnippet.clear();
@@ -1266,8 +1372,8 @@ namespace basegfx::utils
}
// append snippet [fLastDotDashMovingLength, fEdgeLength]
const bool bHandleLine(bIsLine && pLineTarget);
const bool bHandleGap(!bIsLine && pGapTarget);
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine || bHandleGap)
{
@@ -1291,73 +1397,28 @@ namespace basegfx::utils
// append last intermediate results (if exists)
if(aSnippet.count())
{
if(bIsLine && pLineTarget)
const bool bHandleLine(bIsLine && aLineTargetCallback);
const bool bHandleGap(!bIsLine && aGapTargetCallback);
if(bHandleLine)
{
pLineTarget->append(aSnippet);
implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
}
else if(!bIsLine && pGapTarget)
if(bHandleGap)
{
pGapTarget->append(aSnippet);
implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
}
}
// check if start and end polygon may be merged
if(pLineTarget)
if(bIsClosed && aLineTargetCallback)
{
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)))
{
// 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);
}
}
implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine);
}
if(pGapTarget)
if(bIsClosed && aGapTargetCallback)
{
const sal_uInt32 nCount(pGapTarget->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(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);
}
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,