basegfx/source/polygon/b2dpolygontools.cxx | 443 ++++++++++++++++------------ distro-configs/CPLinux-LOKit.conf | 1 include/basegfx/polygon/b2dpolygontools.hxx | 24 + 3 files changed, 289 insertions(+), 179 deletions(-)
New commits: commit ddc81265192f27047f363db0a0ff7dcd493fa4b1 Author: Andras Timar <andras.ti...@collabora.com> AuthorDate: Tue Apr 28 22:50:34 2020 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Tue Apr 28 22:50:34 2020 +0200 add --enable-sal-log to CPLinux-LOKit.conf to help debugging release packages Change-Id: Ia724186ae93038eddb9e11c6678e6d8522846f85 diff --git a/distro-configs/CPLinux-LOKit.conf b/distro-configs/CPLinux-LOKit.conf index 75ad26fc2045..9d5518b66897 100644 --- a/distro-configs/CPLinux-LOKit.conf +++ b/distro-configs/CPLinux-LOKit.conf @@ -62,3 +62,4 @@ --disable-lotuswordpro --disable-lpsolve --enable-symbols +--enable-sal-log commit 55c1016740d901668ae2766a5191b24bddb2d2f5 Author: Armin Le Grand (Collabora) <armin.le.gr...@me.com> AuthorDate: Fri Feb 14 12:32:42 2020 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Tue Apr 28 22:42:40 2020 +0200 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 diff --git a/basegfx/source/polygon/b2dpolygontools.cxx b/basegfx/source/polygon/b2dpolygontools.cxx index e0cfcdba1933..a4fd8378de4d 100644 --- a/basegfx/source/polygon/b2dpolygontools.cxx +++ b/basegfx/source/polygon/b2dpolygontools.cxx @@ -1122,7 +1122,102 @@ namespace basegfx 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()); @@ -1132,154 +1227,159 @@ namespace basegfx 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; + // 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. + static double fNumberOfAllowedSnippets(65535.0 * 2.0); + const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size())); + const double fCandidateLength(basegfx::utils::getLength(rCandidate)); + std::vector<double> aDotDashArray(rDotDashArray); - // 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)); + 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 (!)"); - // check if we have a trivial bezier segment -> possible fallback to edge - aCurrentEdge.testAndSolveTrivialBezier(); + // calculate correcting factor, apply to aDotDashArray and fDotDashLength + // to enlarge these as needed + const double fFactor(fCandidateLength / fAllowedLength); + std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; }); + fDotDashLength *= fFactor; + } - if(aCurrentEdge.isBezier()) - { - // bezier segment - const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge); - const double fEdgeLength(aCubicBezierHelper.getLength()); + // 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)); - 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); + // prepare DotDashArray iteration and the line/gap switching bool + sal_uInt32 nDotDashIndex(0); + bool bIsLine(true); + double fDotDashMovingLength(aDotDashArray[0]); + B2DPolygon aSnippet; - if(bHandleLine || bHandleGap) - { - const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); - const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength)); - B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd)); + // remember 1st and last snippets to try to merge after execution + // is complete and hand to callback + B2DPolygon aFirstLine, aLastLine; + B2DPolygon aFirstGap, aLastGap; - if(!aSnippet.count()) - { - aSnippet.append(aBezierSnippet.getStartPoint()); - } - - aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint()); - - if(bHandleLine) - { - pLineTarget->append(aSnippet); - } - else - { - pGapTarget->append(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)); - aSnippet.clear(); - } + // check if we have a trivial bezier segment -> possible fallback to edge + aCurrentEdge.testAndSolveTrivialBezier(); - // prepare next DotDashArray step and flip line/gap flag - fLastDotDashMovingLength = fDotDashMovingLength; - fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; - bIsLine = !bIsLine; - } + if(aCurrentEdge.isBezier()) + { + // bezier segment + const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge); + const double fEdgeLength(aCubicBezierHelper.getLength()); - // append closing snippet [fLastDotDashMovingLength, fEdgeLength] - const bool bHandleLine(bIsLine && pLineTarget); - const bool bHandleGap(!bIsLine && pGapTarget); + 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) { - 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()); + + 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 += aDotDashArray[(++nDotDashIndex) % nDotDashCount]; + bIsLine = !bIsLine; } - } - 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); + // append closing snippet [fLastDotDashMovingLength, fEdgeLength] + const bool bHandleLine(bIsLine && aLineTargetCallback); + const bool bHandleGap(!bIsLine && aGapTargetCallback); - if(bHandleLine || bHandleGap) - { - if(!aSnippet.count()) - { - aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength)); - } + if(bHandleLine || bHandleGap) + { + B2DCubicBezier aRight; + const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); - aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength)); + aCurrentEdge.split(fBezierSplit, nullptr, &aRight); - if(bHandleLine) - { - pLineTarget->append(aSnippet); - } - else - { - pGapTarget->append(aSnippet); - } + if(!aSnippet.count()) + { + aSnippet.append(aRight.getStartPoint()); + } - aSnippet.clear(); - } + aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint()); + } - // prepare next DotDashArray step and flip line/gap flag - fLastDotDashMovingLength = fDotDashMovingLength; - fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; - bIsLine = !bIsLine; - } + // prepare move to next edge + fDotDashMovingLength -= fEdgeLength; + } + } + else + { + // simple edge + const double fEdgeLength(aCurrentEdge.getEdgeLength()); - // append snippet [fLastDotDashMovingLength, fEdgeLength] - const bool bHandleLine(bIsLine && pLineTarget); - const bool bHandleGap(!bIsLine && pGapTarget); + 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) { @@ -1288,89 +1388,76 @@ namespace basegfx 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()); - } + aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength)); - // append last intermediate results (if exists) - if(aSnippet.count()) - { - if(bIsLine && pLineTarget) - { - pLineTarget->append(aSnippet); - } - else if(!bIsLine && pGapTarget) - { - pGapTarget->append(aSnippet); - } - } + if(bHandleLine) + { + implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine); + } - // check if start and end polygon may be merged - if(pLineTarget) - { - const sal_uInt32 nCount(pLineTarget->count()); + if(bHandleGap) + { + implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap); + } - 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)); + aSnippet.clear(); + } - 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); + // prepare next DotDashArray step and flip line/gap flag + fLastDotDashMovingLength = fDotDashMovingLength; + fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount]; + bIsLine = !bIsLine; } - } - } - - 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)); + // append snippet [fLastDotDashMovingLength, fEdgeLength] + const bool bHandleLine(bIsLine && aLineTargetCallback); + const bool bHandleGap(!bIsLine && aGapTargetCallback); - 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(); - pGapTarget->setB2DPolygon(0, aLast); - pGapTarget->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; } } + + // prepare next edge step (end point gets new start point) + aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); } - else + + // append last intermediate results (if exists) + if(aSnippet.count()) { - // parameters make no sense, just add source to targets - if(pLineTarget) + const bool bHandleLine(bIsLine && aLineTargetCallback); + const bool bHandleGap(!bIsLine && aGapTargetCallback); + + if(bHandleLine) { - pLineTarget->append(rCandidate); + implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine); } - if(pGapTarget) + if(bHandleGap) { - pGapTarget->append(rCandidate); + implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap); } } + + if(bIsClosed && aLineTargetCallback) + { + implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine); + } + + if(bIsClosed && aGapTargetCallback) + { + implHandleFirstLast(aGapTargetCallback, aFirstGap, aLastGap); + } } // test if point is inside epsilon-range around an edge defined diff --git a/include/basegfx/polygon/b2dpolygontools.hxx b/include/basegfx/polygon/b2dpolygontools.hxx index 57b9130b4399..9defd76ef548 100644 --- a/include/basegfx/polygon/b2dpolygontools.hxx +++ b/include/basegfx/polygon/b2dpolygontools.hxx @@ -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, _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits