summaryrefslogtreecommitdiffstats
path: root/basegfx/source/tools
diff options
context:
space:
mode:
Diffstat (limited to 'basegfx/source/tools')
-rw-r--r--basegfx/source/tools/b2dclipstate.cxx480
-rw-r--r--basegfx/source/tools/canvastools.cxx474
-rw-r--r--basegfx/source/tools/gradienttools.cxx471
-rw-r--r--basegfx/source/tools/keystoplerp.cxx91
-rw-r--r--basegfx/source/tools/numbertools.cxx58
-rw-r--r--basegfx/source/tools/stringconversiontools.cxx162
-rw-r--r--basegfx/source/tools/systemdependentdata.cxx199
-rw-r--r--basegfx/source/tools/tools.cxx111
-rw-r--r--basegfx/source/tools/unopolypolygon.cxx456
-rw-r--r--basegfx/source/tools/zoomtools.cxx113
10 files changed, 2615 insertions, 0 deletions
diff --git a/basegfx/source/tools/b2dclipstate.cxx b/basegfx/source/tools/b2dclipstate.cxx
new file mode 100644
index 000000000..aa5f655cc
--- /dev/null
+++ b/basegfx/source/tools/b2dclipstate.cxx
@@ -0,0 +1,480 @@
+/* -*- 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 <basegfx/utils/b2dclipstate.hxx>
+
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2dpolyrange.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+
+
+namespace basegfx::utils
+{
+ class ImplB2DClipState
+ {
+ public:
+ enum Operation {UNION, INTERSECT, XOR, SUBTRACT};
+
+ ImplB2DClipState() :
+ maPendingPolygons(),
+ maPendingRanges(),
+ maClipPoly(),
+ mePendingOps(UNION)
+ {}
+
+ explicit ImplB2DClipState( const B2DPolyPolygon& rPoly ) :
+ maPendingPolygons(),
+ maPendingRanges(),
+ maClipPoly(rPoly),
+ mePendingOps(UNION)
+ {}
+
+ bool isCleared() const
+ {
+ return !maClipPoly.count()
+ && !maPendingPolygons.count()
+ && !maPendingRanges.count();
+ }
+
+ bool isNullClipPoly() const
+ {
+ return maClipPoly.count() == 1
+ && !maClipPoly.getB2DPolygon(0).count();
+ }
+
+ bool isNull() const
+ {
+ return !maPendingPolygons.count()
+ && !maPendingRanges.count()
+ && isNullClipPoly();
+ }
+
+ void makeNull()
+ {
+ maPendingPolygons.clear();
+ maPendingRanges.clear();
+ maClipPoly.clear();
+ maClipPoly.append(B2DPolygon());
+ mePendingOps = UNION;
+ }
+
+ bool operator==(const ImplB2DClipState& rRHS) const
+ {
+ return maPendingPolygons == rRHS.maPendingPolygons
+ && maPendingRanges == rRHS.maPendingRanges
+ && maClipPoly == rRHS.maClipPoly
+ && mePendingOps == rRHS.mePendingOps;
+ }
+
+ void addRange(const B2DRange& rRange, Operation eOp)
+ {
+ if( rRange.isEmpty() )
+ return;
+
+ commitPendingPolygons();
+ if( mePendingOps != eOp )
+ commitPendingRanges();
+
+ mePendingOps = eOp;
+ maPendingRanges.appendElement(
+ rRange,
+ B2VectorOrientation::Positive);
+ }
+
+ void addPolyPolygon(const B2DPolyPolygon& aPoly, Operation eOp)
+ {
+ commitPendingRanges();
+ if( mePendingOps != eOp )
+ commitPendingPolygons();
+
+ mePendingOps = eOp;
+ maPendingPolygons.append(aPoly);
+ }
+
+ void unionRange(const B2DRange& rRange)
+ {
+ if( isCleared() )
+ return;
+
+ addRange(rRange,UNION);
+ }
+
+ void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isCleared() )
+ return;
+
+ addPolyPolygon(rPolyPoly,UNION);
+ }
+
+ void intersectRange(const B2DRange& rRange)
+ {
+ if( isNull() )
+ return;
+
+ addRange(rRange,INTERSECT);
+ }
+
+ void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isNull() )
+ return;
+
+ addPolyPolygon(rPolyPoly,INTERSECT);
+ }
+
+ void subtractRange(const B2DRange& rRange )
+ {
+ if( isNull() )
+ return;
+
+ addRange(rRange,SUBTRACT);
+ }
+
+ void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isNull() )
+ return;
+
+ addPolyPolygon(rPolyPoly,SUBTRACT);
+ }
+
+ void xorRange(const B2DRange& rRange)
+ {
+ addRange(rRange,XOR);
+ }
+
+ void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ addPolyPolygon(rPolyPoly,XOR);
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ maPendingRanges.transform(rTranslate);
+ maPendingPolygons.transform(rTranslate);
+ maClipPoly.transform(rTranslate);
+ }
+
+ B2DPolyPolygon const & getClipPoly() const
+ {
+ commitPendingRanges();
+ commitPendingPolygons();
+
+ return maClipPoly;
+ }
+
+ private:
+ void commitPendingPolygons() const
+ {
+ if( !maPendingPolygons.count() )
+ return;
+
+ // assumption: maClipPoly has kept polygons prepared for
+ // clipping; i.e. no neutral polygons & correct
+ // orientation
+ maPendingPolygons = utils::prepareForPolygonOperation(maPendingPolygons);
+ const bool bIsEmpty=isNullClipPoly();
+ const bool bIsCleared=!maClipPoly.count();
+ switch(mePendingOps)
+ {
+ case UNION:
+ assert( !bIsCleared );
+
+ if( bIsEmpty )
+ maClipPoly = maPendingPolygons;
+ else
+ maClipPoly = utils::solvePolygonOperationOr(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ case INTERSECT:
+ assert( !bIsEmpty );
+
+ if( bIsCleared )
+ maClipPoly = maPendingPolygons;
+ else
+ maClipPoly = utils::solvePolygonOperationAnd(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ case XOR:
+ if( bIsEmpty )
+ maClipPoly = maPendingPolygons;
+ else if( bIsCleared )
+ {
+ // not representable, strictly speaking,
+ // using polygons with the common even/odd
+ // or nonzero winding number fill rule. If
+ // we'd want to represent it, fill rule
+ // would need to be "non-negative winding
+ // number" (and we then would return
+ // 'holes' here)
+
+ // going for an ugly hack meanwhile
+ maClipPoly = utils::solvePolygonOperationXor(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ maPendingPolygons);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationXor(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ case SUBTRACT:
+ assert( !bIsEmpty );
+
+ // first union all pending ones, subtract en bloc then
+ maPendingPolygons = solveCrossovers(maPendingPolygons);
+ maPendingPolygons = stripNeutralPolygons(maPendingPolygons);
+ maPendingPolygons = stripDispensablePolygons(maPendingPolygons);
+
+ if( bIsCleared )
+ {
+ // not representable, strictly speaking,
+ // using polygons with the common even/odd
+ // or nonzero winding number fill rule. If
+ // we'd want to represent it, fill rule
+ // would need to be "non-negative winding
+ // number" (and we then would return
+ // 'holes' here)
+
+ // going for an ugly hack meanwhile
+ maClipPoly = utils::solvePolygonOperationDiff(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ maPendingPolygons);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationDiff(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ }
+
+ maPendingPolygons.clear();
+ mePendingOps = UNION;
+ }
+
+ void commitPendingRanges() const
+ {
+ if( !maPendingRanges.count() )
+ return;
+
+ // use the specialized range clipper for the win
+ B2DPolyPolygon aCollectedRanges;
+ const bool bIsEmpty=isNullClipPoly();
+ const bool bIsCleared=!maClipPoly.count();
+ switch(mePendingOps)
+ {
+ case UNION:
+ assert( !bIsCleared );
+
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
+ if( bIsEmpty )
+ maClipPoly = aCollectedRanges;
+ else
+ maClipPoly = utils::solvePolygonOperationOr(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ case INTERSECT:
+ assert( !bIsEmpty );
+
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ if( maPendingRanges.count() > 1 )
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true);
+
+ if( bIsCleared )
+ maClipPoly = aCollectedRanges;
+ else
+ maClipPoly = utils::solvePolygonOperationAnd(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ case XOR:
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ aCollectedRanges = correctOrientations(aCollectedRanges);
+
+ if( bIsEmpty )
+ maClipPoly = aCollectedRanges;
+ else if( bIsCleared )
+ {
+ // not representable, strictly speaking,
+ // using polygons with the common even/odd
+ // or nonzero winding number fill rule. If
+ // we'd want to represent it, fill rule
+ // would need to be "non-negative winding
+ // number" (and we then would return
+ // 'holes' here)
+
+ // going for an ugly hack meanwhile
+ maClipPoly = utils::solvePolygonOperationXor(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ aCollectedRanges);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationXor(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ case SUBTRACT:
+ assert( !bIsEmpty );
+
+ // first union all pending ranges, subtract en bloc then
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
+
+ if( bIsCleared )
+ {
+ // not representable, strictly speaking,
+ // using polygons with the common even/odd
+ // or nonzero winding number fill rule. If
+ // we'd want to represent it, fill rule
+ // would need to be "non-negative winding
+ // number" (and we then would return
+ // 'holes' here)
+
+ // going for an ugly hack meanwhile
+ maClipPoly = utils::solvePolygonOperationDiff(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ aCollectedRanges);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationDiff(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ }
+
+ maPendingRanges.clear();
+ mePendingOps = UNION;
+ }
+
+ mutable B2DPolyPolygon maPendingPolygons;
+ mutable B2DPolyRange maPendingRanges;
+ mutable B2DPolyPolygon maClipPoly;
+ mutable Operation mePendingOps;
+ };
+
+ B2DClipState::B2DClipState() = default;
+
+ B2DClipState::~B2DClipState() = default;
+
+ B2DClipState::B2DClipState( const B2DClipState& ) = default;
+
+ B2DClipState::B2DClipState( B2DClipState&& ) = default;
+
+ B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) :
+ mpImpl( ImplB2DClipState(rPolyPoly) )
+ {}
+
+ B2DClipState& B2DClipState::operator=( const B2DClipState& ) = default;
+
+ B2DClipState& B2DClipState::operator=( B2DClipState&& ) = default;
+
+ void B2DClipState::makeNull()
+ {
+ mpImpl->makeNull();
+ }
+
+ bool B2DClipState::isCleared() const
+ {
+ return mpImpl->isCleared();
+ }
+
+ bool B2DClipState::operator==(const B2DClipState& rRHS) const
+ {
+ if(mpImpl.same_object(rRHS.mpImpl))
+ return true;
+
+ return ((*mpImpl) == (*rRHS.mpImpl));
+ }
+
+ bool B2DClipState::operator!=(const B2DClipState& rRHS) const
+ {
+ return !(*this == rRHS);
+ }
+
+ void B2DClipState::unionRange(const B2DRange& rRange)
+ {
+ mpImpl->unionRange(rRange);
+ }
+
+ void B2DClipState::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->unionPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::intersectRange(const B2DRange& rRange)
+ {
+ mpImpl->intersectRange(rRange);
+ }
+
+ void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->intersectPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::subtractRange(const B2DRange& rRange)
+ {
+ mpImpl->subtractRange(rRange);
+ }
+
+ void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->subtractPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::xorRange(const B2DRange& rRange)
+ {
+ mpImpl->xorRange(rRange);
+ }
+
+ void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->xorPolyPolygon(rPolyPoly);
+ }
+
+ B2DPolyPolygon const & B2DClipState::getClipPoly() const
+ {
+ return mpImpl->getClipPoly();
+ }
+
+ void B2DClipState::transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ return mpImpl->transform(rTranslate);
+ }
+
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/canvastools.cxx b/basegfx/source/tools/canvastools.cxx
new file mode 100644
index 000000000..11ebe70de
--- /dev/null
+++ b/basegfx/source/tools/canvastools.cxx
@@ -0,0 +1,474 @@
+/* -*- 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 <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/geometry/RealPoint2D.hpp>
+#include <com/sun/star/geometry/RealRectangle2D.hpp>
+#include <com/sun/star/geometry/RealRectangle3D.hpp>
+#include <com/sun/star/geometry/RealBezierSegment2D.hpp>
+#include <com/sun/star/geometry/AffineMatrix2D.hpp>
+#include <com/sun/star/geometry/AffineMatrix3D.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <com/sun/star/geometry/IntegerRectangle2D.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
+#include <com/sun/star/rendering/XGraphicDevice.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <basegfx/utils/unopolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/utils/canvastools.hxx>
+
+using namespace ::com::sun::star;
+
+namespace basegfx::unotools
+{
+ namespace
+ {
+ uno::Sequence< geometry::RealBezierSegment2D > bezierSequenceFromB2DPolygon(const ::basegfx::B2DPolygon& rPoly)
+ {
+ const sal_uInt32 nPointCount(rPoly.count());
+ uno::Sequence< geometry::RealBezierSegment2D > outputSequence(nPointCount);
+ geometry::RealBezierSegment2D* pOutput = outputSequence.getArray();
+
+ // fill sequences and imply closed polygon on this implementation layer
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const basegfx::B2DPoint aStart(rPoly.getB2DPoint(a));
+ const basegfx::B2DPoint aControlA(rPoly.getNextControlPoint(a));
+ const basegfx::B2DPoint aControlB(rPoly.getPrevControlPoint((a + 1) % nPointCount));
+
+ pOutput[a] = geometry::RealBezierSegment2D(
+ aStart.getX(), aStart.getY(),
+ aControlA.getX(), aControlA.getY(),
+ aControlB.getX(), aControlB.getY());
+ }
+
+ return outputSequence;
+ }
+
+ uno::Sequence< geometry::RealPoint2D > pointSequenceFromB2DPolygon( const ::basegfx::B2DPolygon& rPoly )
+ {
+ const sal_uInt32 nNumPoints( rPoly.count() );
+
+ uno::Sequence< geometry::RealPoint2D > outputSequence( nNumPoints );
+ geometry::RealPoint2D* pOutput = outputSequence.getArray();
+
+ // fill sequence from polygon
+ sal_uInt32 i;
+ for( i=0; i<nNumPoints; ++i )
+ {
+ const ::basegfx::B2DPoint aPoint( rPoly.getB2DPoint(i) );
+
+ pOutput[i] = geometry::RealPoint2D( aPoint.getX(),
+ aPoint.getY() );
+ }
+
+ return outputSequence;
+ }
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > bezierSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence( nNumPolies );
+ uno::Sequence< geometry::RealBezierSegment2D >* pOutput = outputSequence.getArray();
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ pOutput[i] = bezierSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) );
+ }
+
+ return outputSequence;
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > pointSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence( nNumPolies );
+ uno::Sequence< geometry::RealPoint2D >* pOutput = outputSequence.getArray();
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ pOutput[i] = pointSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) );
+ }
+
+ return outputSequence;
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice,
+ const ::basegfx::B2DPolygon& rPoly )
+ {
+ uno::Reference< rendering::XPolyPolygon2D > xRes;
+
+ if( !xGraphicDevice.is() )
+ return xRes;
+
+ if( rPoly.areControlPointsUsed() )
+ {
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence{ bezierSequenceFromB2DPolygon( rPoly )};
+
+ xRes = xGraphicDevice->createCompatibleBezierPolyPolygon( outputSequence );
+ }
+ else
+ {
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence{
+ pointSequenceFromB2DPolygon( rPoly )};
+
+ xRes = xGraphicDevice->createCompatibleLinePolyPolygon( outputSequence );
+ }
+
+ if( xRes.is() && rPoly.isClosed() )
+ xRes->setClosed( 0, true );
+
+ return xRes;
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolyPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice,
+ const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ uno::Reference< rendering::XPolyPolygon2D > xRes;
+
+ if( !xGraphicDevice.is() )
+ return xRes;
+
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ if( rPolyPoly.areControlPointsUsed() )
+ {
+ xRes = xGraphicDevice->createCompatibleBezierPolyPolygon(
+ bezierSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) );
+ }
+ else
+ {
+ xRes = xGraphicDevice->createCompatibleLinePolyPolygon(
+ pointSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) );
+ }
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ xRes->setClosed( i, rPolyPoly.getB2DPolygon(i).isClosed() );
+ }
+
+ return xRes;
+ }
+
+ ::basegfx::B2DPolygon polygonFromPoint2DSequence( const uno::Sequence< geometry::RealPoint2D >& points )
+ {
+ const sal_Int32 nCurrSize( points.getLength() );
+
+ ::basegfx::B2DPolygon aPoly;
+
+ for( sal_Int32 nCurrPoint=0; nCurrPoint<nCurrSize; ++nCurrPoint )
+ aPoly.append( b2DPointFromRealPoint2D( points[nCurrPoint] ) );
+
+ return aPoly;
+ }
+
+ ::basegfx::B2DPolyPolygon polyPolygonFromPoint2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points )
+ {
+ ::basegfx::B2DPolyPolygon aRes;
+
+ for( const auto & p : points )
+ {
+ aRes.append( polygonFromPoint2DSequence( p ) );
+ }
+
+ return aRes;
+ }
+
+ ::basegfx::B2DPolygon polygonFromBezier2DSequence( const uno::Sequence< geometry::RealBezierSegment2D >& curves )
+ {
+ const sal_Int32 nSize(curves.getLength());
+ basegfx::B2DPolygon aRetval;
+
+ if(nSize)
+ {
+ // prepare start with providing a start point. Use the first point from
+ // the sequence for this
+ const geometry::RealBezierSegment2D& rFirstSegment(curves[0]); // #i79917# first segment, not last
+ aRetval.append(basegfx::B2DPoint(rFirstSegment.Px, rFirstSegment.Py));
+
+ for(sal_Int32 a(0); a < nSize; a++)
+ {
+ const geometry::RealBezierSegment2D& rCurrSegment(curves[a]);
+ const geometry::RealBezierSegment2D& rNextSegment(curves[(a + 1) % nSize]);
+
+ // append curved edge with the control points and the next point
+ aRetval.appendBezierSegment(
+ basegfx::B2DPoint(rCurrSegment.C1x, rCurrSegment.C1y),
+ basegfx::B2DPoint(rCurrSegment.C2x, rCurrSegment.C2y), // #i79917# Argh! An x for an y!!
+ basegfx::B2DPoint(rNextSegment.Px, rNextSegment.Py));
+ }
+
+ // rescue the control point and remove the now double-added point
+ aRetval.setPrevControlPoint(0, aRetval.getPrevControlPoint(aRetval.count() - 1));
+ aRetval.remove(aRetval.count() - 1);
+ }
+
+ return aRetval;
+ }
+
+ ::basegfx::B2DPolyPolygon polyPolygonFromBezier2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& curves )
+ {
+ ::basegfx::B2DPolyPolygon aRes;
+
+ for( const auto & c : curves )
+ {
+ aRes.append( polygonFromBezier2DSequence( c ) );
+ }
+
+ return aRes;
+ }
+
+ ::basegfx::B2DPolyPolygon b2DPolyPolygonFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly )
+ {
+ ::basegfx::unotools::UnoPolyPolygon* pPolyImpl =
+ dynamic_cast< ::basegfx::unotools::UnoPolyPolygon* >( xPoly.get() );
+
+ if( pPolyImpl )
+ {
+ return pPolyImpl->getPolyPolygon();
+ }
+ else
+ {
+ // not a known implementation object - try data source
+ // interfaces
+ const sal_Int32 nPolys( xPoly->getNumberOfPolygons() );
+
+ uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly(
+ xPoly,
+ uno::UNO_QUERY );
+
+ if( xBezierPoly.is() )
+ {
+ return ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence(
+ xBezierPoly->getBezierSegments( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ else
+ {
+ uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly(
+ xPoly,
+ uno::UNO_QUERY );
+
+ // no implementation class and no data provider
+ // found - contract violation.
+ if( !xLinePoly.is() )
+ {
+ throw lang::IllegalArgumentException(
+ "basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(): Invalid input"
+ "poly-polygon, cannot retrieve vertex data",
+ uno::Reference< uno::XInterface >(),
+ 0 );
+ }
+
+ return ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence(
+ xLinePoly->getPoints( 0,
+ nPolys,
+ 0,
+ -1 ));
+ }
+ }
+ }
+
+ ::basegfx::B2DHomMatrix& homMatrixFromAffineMatrix( ::basegfx::B2DHomMatrix& output,
+ const geometry::AffineMatrix2D& input )
+ {
+ // ensure last row is [0,0,1] (and optimized away)
+ output.identity();
+
+ output.set(0,0, input.m00);
+ output.set(0,1, input.m01);
+ output.set(0,2, input.m02);
+ output.set(1,0, input.m10);
+ output.set(1,1, input.m11);
+ output.set(1,2, input.m12);
+
+ return output;
+ }
+
+ ::basegfx::B3DHomMatrix homMatrixFromAffineMatrix3D( const ::css::geometry::AffineMatrix3D& input )
+ {
+ ::basegfx::B3DHomMatrix output;
+
+ output.set(0,0, input.m00);
+ output.set(0,1, input.m01);
+ output.set(0,2, input.m02);
+ output.set(0,3, input.m03);
+
+ output.set(1,0, input.m10);
+ output.set(1,1, input.m11);
+ output.set(1,2, input.m12);
+ output.set(1,3, input.m13);
+
+ output.set(2,0, input.m20);
+ output.set(2,1, input.m21);
+ output.set(2,2, input.m22);
+ output.set(2,3, input.m23);
+
+ return output;
+ }
+
+ geometry::AffineMatrix2D& affineMatrixFromHomMatrix( geometry::AffineMatrix2D& output,
+ const ::basegfx::B2DHomMatrix& input)
+ {
+ output.m00 = input.get(0,0);
+ output.m01 = input.get(0,1);
+ output.m02 = input.get(0,2);
+ output.m10 = input.get(1,0);
+ output.m11 = input.get(1,1);
+ output.m12 = input.get(1,2);
+
+ return output;
+ }
+
+ geometry::AffineMatrix3D& affineMatrixFromHomMatrix3D(
+ geometry::AffineMatrix3D& output,
+ const ::basegfx::B3DHomMatrix& input)
+ {
+ output.m00 = input.get(0,0);
+ output.m01 = input.get(0,1);
+ output.m02 = input.get(0,2);
+ output.m03 = input.get(0,3);
+
+ output.m10 = input.get(1,0);
+ output.m11 = input.get(1,1);
+ output.m12 = input.get(1,2);
+ output.m13 = input.get(1,3);
+
+ output.m20 = input.get(2,0);
+ output.m21 = input.get(2,1);
+ output.m22 = input.get(2,2);
+ output.m23 = input.get(2,3);
+
+ return output;
+ }
+
+ geometry::RealSize2D size2DFromB2DSize( const ::basegfx::B2DVector& rVec )
+ {
+ return geometry::RealSize2D( rVec.getX(),
+ rVec.getY() );
+ }
+
+ geometry::RealPoint2D point2DFromB2DPoint( const ::basegfx::B2DPoint& rPoint )
+ {
+ return geometry::RealPoint2D( rPoint.getX(),
+ rPoint.getY() );
+ }
+
+ geometry::RealRectangle2D rectangle2DFromB2DRectangle( const ::basegfx::B2DRange& rRect )
+ {
+ return geometry::RealRectangle2D( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMaxX(),
+ rRect.getMaxY() );
+ }
+
+ geometry::RealRectangle3D rectangle3DFromB3DRectangle( const ::basegfx::B3DRange& rRect )
+ {
+ return geometry::RealRectangle3D( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMinZ(),
+ rRect.getMaxX(),
+ rRect.getMaxY(),
+ rRect.getMaxZ());
+ }
+
+ ::basegfx::B2DPoint b2DPointFromRealPoint2D( const geometry::RealPoint2D& rPoint )
+ {
+ return ::basegfx::B2DPoint( rPoint.X,
+ rPoint.Y );
+ }
+
+ ::basegfx::B2DRange b2DRectangleFromRealRectangle2D( const geometry::RealRectangle2D& rRect )
+ {
+ return ::basegfx::B2DRange( rRect.X1,
+ rRect.Y1,
+ rRect.X2,
+ rRect.Y2 );
+ }
+
+ ::basegfx::B3DRange b3DRectangleFromRealRectangle3D( const geometry::RealRectangle3D& rRect )
+ {
+ return ::basegfx::B3DRange( rRect.X1,
+ rRect.Y1,
+ rRect.Z1,
+ rRect.X2,
+ rRect.Y2,
+ rRect.Z2);
+ }
+
+ geometry::IntegerSize2D integerSize2DFromB2ISize( const ::basegfx::B2IVector& rSize )
+ {
+ return geometry::IntegerSize2D( rSize.getX(),
+ rSize.getY() );
+ }
+
+ ::basegfx::B2IVector b2ISizeFromIntegerSize2D( const geometry::IntegerSize2D& rSize )
+ {
+ return ::basegfx::B2IVector( rSize.Width,
+ rSize.Height );
+ }
+
+ ::basegfx::B2IRange b2IRectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRectangle )
+ {
+ return ::basegfx::B2IRange( rRectangle.X1, rRectangle.Y1,
+ rRectangle.X2, rRectangle.Y2 );
+ }
+
+ ::basegfx::B2IRange b2IRectangleFromAwtRectangle( const awt::Rectangle& rRect )
+ {
+ return ::basegfx::B2IRange( rRect.X,
+ rRect.Y,
+ rRect.X + rRect.Width,
+ rRect.Y + rRect.Height );
+ }
+
+ ::basegfx::B2IRange b2ISurroundingRangeFromB2DRange( const ::basegfx::B2DRange& rRange )
+ {
+ return ::basegfx::B2IRange( static_cast<sal_Int32>( floor(rRange.getMinX()) ),
+ static_cast<sal_Int32>( floor(rRange.getMinY()) ),
+ static_cast<sal_Int32>( ceil(rRange.getMaxX()) ),
+ static_cast<sal_Int32>( ceil(rRange.getMaxY()) ) );
+ }
+
+ ::basegfx::B2DRange b2DSurroundingIntegerRangeFromB2DRange( const ::basegfx::B2DRange& rRange )
+ {
+ return ::basegfx::B2DRange( floor(rRange.getMinX()),
+ floor(rRange.getMinY()),
+ ceil(rRange.getMaxX()),
+ ceil(rRange.getMaxY()) );
+ }
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx
new file mode 100644
index 000000000..082f56455
--- /dev/null
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -0,0 +1,471 @@
+/* -*- 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 <basegfx/utils/gradienttools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+namespace basegfx
+{
+ bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
+ {
+ return getTextureTransform() == rODFGradientInfo.getTextureTransform()
+ && getAspectRatio() == rODFGradientInfo.getAspectRatio()
+ && getSteps() == rODFGradientInfo.getSteps();
+ }
+
+ const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const
+ {
+ if(maBackTextureTransform.isIdentity())
+ {
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform = getTextureTransform();
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform.invert();
+ }
+
+ return maBackTextureTransform;
+ }
+
+ /** Most of the setup for linear & axial gradient is the same, except
+ for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo init1DGradientInfo(
+ const B2DRange& rTargetRange,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bAxial)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fSizeWithoutBorder(1.0 - fBorder);
+
+ if(bAxial)
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder * 0.5);
+ aTextureTransform.translate(0.0, 0.5);
+ }
+ else
+ {
+ if(!fTools::equal(fSizeWithoutBorder, 1.0))
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder);
+ aTextureTransform.translate(0.0, fBorder);
+ }
+ }
+
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTools::equalZero(fTargetSizeY) ? 1.0 : fTargetSizeX / fTargetSizeY);
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Most of the setup for radial & ellipsoidal gradient is the same,
+ except for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo initEllipticalGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bCircular)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bCircular)
+ {
+ const double fOriginalDiag(sqrt((fTargetSizeX * fTargetSizeX) + (fTargetSizeY * fTargetSizeY)));
+
+ fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0;
+ fTargetSizeX = fOriginalDiag;
+ fTargetSizeY = fOriginalDiag;
+ }
+ else
+ {
+ fTargetOffsetX -= (0.4142 / 2.0 ) * fTargetSizeX;
+ fTargetOffsetY -= (0.4142 / 2.0 ) * fTargetSizeY;
+ fTargetSizeX = 1.4142 * fTargetSizeX;
+ fTargetSizeY = 1.4142 * fTargetSizeY;
+ }
+
+ const double fHalfBorder((1.0 - fBorder) * 0.5);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(!bCircular && !fTools::equalZero(fAngle))
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use original target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
+ fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Setup for rect & square gradient is exactly the same. Factored out
+ here.
+ */
+ static ODFGradientInfo initRectGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bSquare)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bSquare)
+ {
+ const double fSquareWidth(std::max(fTargetSizeX, fTargetSizeY));
+
+ fTargetOffsetX -= (fSquareWidth - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fSquareWidth - fTargetSizeY) / 2.0;
+ fTargetSizeX = fTargetSizeY = fSquareWidth;
+ }
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fHalfBorder((1.0 - fBorder) * 0.5);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use scaled target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * fTargetSizeX;
+ fTargetOffsetY += (rOffset.getY() - 0.5) * fTargetSizeY;
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ namespace utils
+ {
+ ODFGradientInfo createLinearODFGradientInfo(
+ const B2DRange& rTargetArea,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createAxialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRadialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ 0.0,
+ true);
+ }
+
+ ODFGradientInfo createEllipticalODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createSquareODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRectangularODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore Y, this is not needed at all for Y-Oriented gradients
+ // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ // {
+ // return 0.0;
+ // }
+
+ if(aCoor.getY() <= 0.0)
+ {
+ return 0.0; // start value for inside
+ }
+
+ if(aCoor.getY() >= 1.0)
+ {
+ return 1.0; // end value for outside
+ }
+
+ const sal_uInt32 nSteps(rGradInfo.getSteps());
+
+ if(nSteps)
+ {
+ return floor(aCoor.getY() * nSteps) / double(nSteps - 1);
+ }
+
+ return aCoor.getY();
+ }
+
+ double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore Y, this is not needed at all for Y-Oriented gradients
+ //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ //{
+ // return 0.0;
+ //}
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 1.0; // use end value when outside in Y
+ }
+
+ const sal_uInt32 nSteps(rGradInfo.getSteps());
+
+ if(nSteps)
+ {
+ return floor(fAbsY * nSteps) / double(nSteps - 1);
+ }
+
+ return fAbsY;
+ }
+
+ double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
+ {
+ return 0.0;
+ }
+
+ const double t(1.0 - sqrt(aCoor.getX() * aCoor.getX() + aCoor.getY() * aCoor.getY()));
+ const sal_uInt32 nSteps(rGradInfo.getSteps());
+
+ if(nSteps && t < 1.0)
+ {
+ return floor(t * nSteps) / double(nSteps - 1);
+ }
+
+ return t;
+ }
+
+ double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ return getRadialGradientAlpha(rUV, rGradInfo); // only matrix setup differs
+ }
+
+ double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+ const double fAbsX(fabs(aCoor.getX()));
+
+ if(fAbsX >= 1.0)
+ {
+ return 0.0;
+ }
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 0.0;
+ }
+
+ const double t(1.0 - std::max(fAbsX, fAbsY));
+ const sal_uInt32 nSteps(rGradInfo.getSteps());
+
+ if(nSteps && t < 1.0)
+ {
+ return floor(t * nSteps) / double(nSteps - 1);
+ }
+
+ return t;
+ }
+
+ double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ return getSquareGradientAlpha(rUV, rGradInfo); // only matrix setup differs
+ }
+ } // namespace utils
+} // namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/keystoplerp.cxx b/basegfx/source/tools/keystoplerp.cxx
new file mode 100644
index 000000000..725d52cf4
--- /dev/null
+++ b/basegfx/source/tools/keystoplerp.cxx
@@ -0,0 +1,91 @@
+/* -*- 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 <basegfx/utils/keystoplerp.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+static void validateInput(const std::vector<double>& rKeyStops)
+{
+#ifdef DBG_UTIL
+ OSL_ENSURE( rKeyStops.size() > 1,
+ "KeyStopLerp::KeyStopLerp(): key stop vector must have two entries or more" );
+
+ // rKeyStops must be sorted in ascending order
+ for( std::size_t i=1, len=rKeyStops.size(); i<len; ++i )
+ {
+ if( rKeyStops[i-1] > rKeyStops[i] )
+ OSL_FAIL( "KeyStopLerp::KeyStopLerp(): time vector is not sorted in ascending order!" );
+ }
+#else
+ (void)rKeyStops;
+#endif
+}
+
+namespace basegfx::utils
+{
+ KeyStopLerp::KeyStopLerp( const std::vector<double>& rKeyStops ) :
+ maKeyStops(rKeyStops),
+ mnLastIndex(0)
+ {
+ validateInput(maKeyStops);
+ }
+
+ KeyStopLerp::KeyStopLerp( const ::css::uno::Sequence<double>& rKeyStops ) :
+ maKeyStops(rKeyStops.getLength()),
+ mnLastIndex(0)
+ {
+ std::copy( rKeyStops.begin(), rKeyStops.end(), maKeyStops.begin() );
+ validateInput(maKeyStops);
+ }
+
+ KeyStopLerp::ResultType KeyStopLerp::lerp(double fAlpha) const
+ {
+ // cached value still okay?
+ if( maKeyStops.at(mnLastIndex) < fAlpha ||
+ maKeyStops.at(mnLastIndex+1) >= fAlpha )
+ {
+ // nope, find new index
+ mnLastIndex = std::min<std::ptrdiff_t>(
+ maKeyStops.size()-2,
+ // range is ensured by max below
+ std::max<std::ptrdiff_t>(
+ 0,
+ std::distance( maKeyStops.begin(),
+ std::lower_bound( maKeyStops.begin(),
+ maKeyStops.end(),
+ fAlpha )) - 1 ));
+ }
+
+ // lerp between stop and stop+1
+ const double fRawLerp=
+ (fAlpha-maKeyStops.at(mnLastIndex)) /
+ (maKeyStops.at(mnLastIndex+1) - maKeyStops.at(mnLastIndex));
+
+ // clamp to permissible range (input fAlpha might be
+ // everything)
+ return ResultType(
+ mnLastIndex,
+ std::clamp(fRawLerp,0.0,1.0));
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/numbertools.cxx b/basegfx/source/tools/numbertools.cxx
new file mode 100644
index 000000000..4248068b7
--- /dev/null
+++ b/basegfx/source/tools/numbertools.cxx
@@ -0,0 +1,58 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/tools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx::utils
+{
+ B2DPolyPolygon number2PolyPolygon(double fValue, sal_Int32 nTotalDigits, sal_Int32 nDecPlaces, bool bLitSegments)
+ {
+ // config here
+ // {
+ const double fSpace=0.2;
+ // }
+ // config here
+
+ OStringBuffer aNum;
+ rtl::math::doubleToStringBuffer(aNum,
+ fValue,
+ rtl_math_StringFormat_F,
+ nDecPlaces, '.',
+ nullptr, ',');
+
+ B2DPolyPolygon aRes;
+ B2DHomMatrix aMat;
+ double fCurrX=std::max(nTotalDigits-aNum.getLength(),
+ sal_Int32(0)) * (1.0+fSpace);
+ for( sal_Int32 i=0; i<aNum.getLength(); ++i )
+ {
+ B2DPolyPolygon aCurr=createSevenSegmentPolyPolygon(aNum[i],
+ bLitSegments);
+
+ aMat.identity();
+ aMat.translate(fCurrX,0.0);
+ aCurr.transform(aMat);
+
+ fCurrX += 1.0+fSpace;
+
+ aRes.append(aCurr);
+ }
+
+ return aRes;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/stringconversiontools.cxx b/basegfx/source/tools/stringconversiontools.cxx
new file mode 100644
index 000000000..079966b27
--- /dev/null
+++ b/basegfx/source/tools/stringconversiontools.cxx
@@ -0,0 +1,162 @@
+/* -*- 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 <stringconversiontools.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx::internal
+{
+ void skipSpaces(sal_Int32& io_rPos,
+ const OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ while( io_rPos < nLen &&
+ rStr[io_rPos] == ' ' )
+ {
+ ++io_rPos;
+ }
+ }
+
+ static void skipSpacesAndCommas(sal_Int32& io_rPos,
+ const OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ while(io_rPos < nLen
+ && (rStr[io_rPos] == ' ' || rStr[io_rPos] == ','))
+ {
+ ++io_rPos;
+ }
+ }
+
+ static bool getDoubleChar(double& o_fRetval,
+ sal_Int32& io_rPos,
+ const OUString& rStr)
+ {
+ sal_Unicode aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ OUStringBuffer sNumberString;
+
+ // sign
+ if(aChar == '+' || aChar == '-')
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ // numbers before point
+ while('0' <= aChar && '9' >= aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ }
+
+ // point
+ if(aChar == '.')
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ }
+
+ // numbers after point
+ while ('0' <= aChar && '9' >= aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ }
+
+ // 'e'
+ if(aChar == 'e' || aChar == 'E')
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+
+ // sign for 'e'
+ if(aChar == '+' || aChar == '-')
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ }
+
+ // number for 'e'
+ while('0' <= aChar && '9' >= aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ io_rPos++;
+ aChar = io_rPos < rStr.getLength() ? rStr[io_rPos] : 0;
+ }
+ }
+
+ if(sNumberString.getLength())
+ {
+ rtl_math_ConversionStatus eStatus;
+ o_fRetval = ::rtl::math::stringToDouble( sNumberString.makeStringAndClear(),
+ '.',
+ ',',
+ &eStatus );
+ return ( eStatus == rtl_math_ConversionStatus_Ok );
+ }
+
+ return false;
+ }
+
+ bool importDoubleAndSpaces(double& o_fRetval,
+ sal_Int32& io_rPos,
+ const OUString& rStr,
+ const sal_Int32 nLen )
+ {
+ if( !getDoubleChar(o_fRetval, io_rPos, rStr) )
+ return false;
+
+ skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+ bool importFlagAndSpaces(sal_Int32& o_nRetval,
+ sal_Int32& io_rPos,
+ const OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ sal_Unicode aChar( rStr[io_rPos] );
+
+ if(aChar == '0')
+ {
+ o_nRetval = 0;
+ ++io_rPos;
+ }
+ else if (aChar == '1')
+ {
+ o_nRetval = 1;
+ ++io_rPos;
+ }
+ else
+ return false;
+
+ skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/systemdependentdata.cxx b/basegfx/source/tools/systemdependentdata.cxx
new file mode 100644
index 000000000..8dbe89156
--- /dev/null
+++ b/basegfx/source/tools/systemdependentdata.cxx
@@ -0,0 +1,199 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <math.h>
+
+namespace basegfx
+{
+ SystemDependentDataManager::SystemDependentDataManager()
+ {
+ }
+
+ SystemDependentDataManager::~SystemDependentDataManager()
+ {
+ }
+} // namespace basegfx
+
+namespace basegfx
+{
+ MinimalSystemDependentDataManager::MinimalSystemDependentDataManager()
+ : SystemDependentDataManager(),
+ maSystemDependentDataReferences()
+ {
+ }
+
+ MinimalSystemDependentDataManager::~MinimalSystemDependentDataManager()
+ {
+ }
+
+ void MinimalSystemDependentDataManager::startUsage(basegfx::SystemDependentData_SharedPtr& rData)
+ {
+ if(rData)
+ {
+ maSystemDependentDataReferences.insert(rData);
+ }
+ }
+
+ void MinimalSystemDependentDataManager::endUsage(basegfx::SystemDependentData_SharedPtr& rData)
+ {
+ if(rData)
+ {
+ maSystemDependentDataReferences.erase(rData);
+ }
+ }
+
+ void MinimalSystemDependentDataManager::touchUsage(basegfx::SystemDependentData_SharedPtr& /* rData */)
+ {
+ }
+
+ void MinimalSystemDependentDataManager::flushAll()
+ {
+ maSystemDependentDataReferences.clear();
+ }
+} // namespace basegfx
+
+namespace basegfx
+{
+ SystemDependentData::SystemDependentData(
+ SystemDependentDataManager& rSystemDependentDataManager)
+ : mrSystemDependentDataManager(rSystemDependentDataManager),
+ mnCalculatedCycles(0)
+ {
+ }
+
+ SystemDependentData::~SystemDependentData()
+ {
+ }
+
+ sal_uInt32 SystemDependentData::calculateCombinedHoldCyclesInSeconds() const
+ {
+ if(0 == mnCalculatedCycles)
+ {
+ const sal_Int64 nBytes(estimateUsageInBytes());
+
+ // tdf#129845 as indicator for no need to buffer trivial data, stay at and
+ // return zero. As border, use 450 bytes. For polygons, this means to buffer
+ // starting with ca. 50 points (GDIPLUS uses 9 bytes per coordinate). For
+ // Bitmap data this means to more or less always buffer (as it was before).
+ // For the future, a more sophisticated differentiation may be added
+ if(nBytes > 450)
+ {
+ const sal_uInt32 nSeconds = 60; // HoldCyclesInSeconds
+
+ // default is Seconds (minimal is one)
+ sal_uInt32 nResult(0 == nSeconds ? 1 : nSeconds);
+
+ if(0 != nBytes)
+ {
+ // use sqrt to get some curved shape. With a default of 60s we get
+ // a single second at 3600 byte. To get close to 10mb, multiply by
+ // a corresponding scaling factor
+ const double fScaleToMB(3600.0 / (1024.0 * 1024.0 * 10.0));
+
+ // also use a multiplier to move the start point higher
+ const double fMultiplierSeconds(10.0);
+
+ // calculate
+ nResult = static_cast<sal_uInt32>((fMultiplierSeconds * nSeconds) / sqrt(nBytes * fScaleToMB));
+
+ // minimal value is 1
+ if(nResult < 1)
+ {
+ nResult = 1;
+ }
+
+ // maximal value is nSeconds
+ if(nResult > nSeconds)
+ {
+ nResult = nSeconds;
+ }
+ }
+
+ // set locally (once, on-demand created, non-zero)
+ const_cast<SystemDependentData*>(this)->mnCalculatedCycles = nResult;
+ }
+ }
+
+ return mnCalculatedCycles;
+ }
+
+ sal_Int64 SystemDependentData::estimateUsageInBytes() const
+ {
+ // default implementation has no idea
+ return 0;
+ }
+} // namespace basegfx
+
+namespace basegfx
+{
+ SystemDependentDataHolder::SystemDependentDataHolder()
+ : maSystemDependentReferences()
+ {
+ }
+
+ SystemDependentDataHolder::~SystemDependentDataHolder()
+ {
+ for(const auto& candidate : maSystemDependentReferences)
+ {
+ basegfx::SystemDependentData_SharedPtr aData(candidate.second.lock());
+
+ if(aData)
+ {
+ aData->getSystemDependentDataManager().endUsage(aData);
+ }
+ }
+ }
+
+ void SystemDependentDataHolder::addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
+ {
+ const size_t hash_code(typeid(*rData).hash_code());
+ auto result(maSystemDependentReferences.find(hash_code));
+
+ if(result != maSystemDependentReferences.end())
+ {
+ basegfx::SystemDependentData_SharedPtr aData(result->second.lock());
+
+ if(aData)
+ {
+ aData->getSystemDependentDataManager().endUsage(aData);
+ }
+
+ maSystemDependentReferences.erase(result);
+ result = maSystemDependentReferences.end();
+ }
+
+ maSystemDependentReferences[hash_code] = rData;
+ rData->getSystemDependentDataManager().startUsage(rData);
+ }
+
+ SystemDependentData_SharedPtr SystemDependentDataHolder::getSystemDependentData(size_t hash_code) const
+ {
+ basegfx::SystemDependentData_SharedPtr aRetval;
+ auto result(maSystemDependentReferences.find(hash_code));
+
+ if(result != maSystemDependentReferences.end())
+ {
+ aRetval = result->second.lock();
+
+ if(aRetval)
+ {
+ aRetval->getSystemDependentDataManager().touchUsage(aRetval);
+ }
+ else
+ {
+ const_cast< SystemDependentDataHolder* >(this)->maSystemDependentReferences.erase(result);
+ }
+ }
+
+ return aRetval;
+ }
+} // namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/tools.cxx b/basegfx/source/tools/tools.cxx
new file mode 100644
index 000000000..4a7f2c962
--- /dev/null
+++ b/basegfx/source/tools/tools.cxx
@@ -0,0 +1,111 @@
+/* -*- 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 <basegfx/utils/tools.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#include <algorithm>
+
+namespace basegfx::utils
+{
+ namespace
+ {
+ double distance( const double& nX,
+ const double& nY,
+ const ::basegfx::B2DVector& rNormal,
+ const double& nC )
+ {
+ return nX*rNormal.getX() + nY*rNormal.getY() - nC;
+ }
+
+ void moveLineOutsideRect( ::basegfx::B2DPoint& io_rStart,
+ ::basegfx::B2DPoint& io_rEnd,
+ const ::basegfx::B2DVector& rMoveDirection,
+ const ::basegfx::B2DRange& rFitTarget )
+ {
+ // calc c for normal line form equation n x - c = 0
+ const double nC( rMoveDirection.scalar( io_rStart ) );
+
+ // calc maximum orthogonal distance for all four bound
+ // rect corners to the line
+ const double nMaxDistance( std::max(
+ 0.0,
+ std::max(
+ distance(rFitTarget.getMinX(),
+ rFitTarget.getMinY(),
+ rMoveDirection,
+ nC),
+ std::max(
+ distance(rFitTarget.getMinX(),
+ rFitTarget.getMaxY(),
+ rMoveDirection,
+ nC),
+ std::max(
+ distance(rFitTarget.getMaxX(),
+ rFitTarget.getMinY(),
+ rMoveDirection,
+ nC),
+ distance(rFitTarget.getMaxX(),
+ rFitTarget.getMaxY(),
+ rMoveDirection,
+ nC) ) ) ) ) );
+
+ // now move line points, such that the bound rect
+ // points are all either 'on' or on the negative side
+ // of the half-plane
+ io_rStart += nMaxDistance*rMoveDirection;
+ io_rEnd += nMaxDistance*rMoveDirection;
+ }
+ }
+
+ void infiniteLineFromParallelogram( ::basegfx::B2DPoint& io_rLeftTop,
+ ::basegfx::B2DPoint& io_rLeftBottom,
+ ::basegfx::B2DPoint& io_rRightTop,
+ ::basegfx::B2DPoint& io_rRightBottom,
+ const ::basegfx::B2DRange& rFitTarget )
+ {
+ // For the top and bottom border line of the
+ // parallelogram, we determine the distance to all four
+ // corner points of the bound rect (tl, tr, bl, br). When
+ // using the unit normal form for lines (n x - c = 0), and
+ // choosing n to point 'outwards' the parallelogram, then
+ // all bound rect corner points having positive distance
+ // to the line lie outside the extended gradient rect, and
+ // thus, the corresponding border line must be moved the
+ // maximum distance outwards.
+
+ // don't use the top and bottom border line direction, and
+ // calculate the normal from them. Instead, use the
+ // vertical lines (lt - lb or rt - rb), as they more
+ // faithfully represent the direction of the
+ // to-be-generated infinite line
+ ::basegfx::B2DVector aDirectionVertical( io_rLeftTop - io_rLeftBottom );
+ aDirectionVertical.normalize();
+
+ const ::basegfx::B2DVector aNormalTop( aDirectionVertical );
+ const ::basegfx::B2DVector aNormalBottom( -aDirectionVertical );
+
+ // now extend parallelogram, such that the bound rect
+ // point are included
+ moveLineOutsideRect( io_rLeftTop, io_rRightTop, aNormalTop, rFitTarget );
+ moveLineOutsideRect( io_rLeftBottom, io_rRightBottom, aNormalBottom, rFitTarget );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/unopolypolygon.cxx b/basegfx/source/tools/unopolypolygon.cxx
new file mode 100644
index 000000000..c05f50f62
--- /dev/null
+++ b/basegfx/source/tools/unopolypolygon.cxx
@@ -0,0 +1,456 @@
+/* -*- 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 <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/utils/unopolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace ::com::sun::star;
+
+namespace basegfx::unotools
+{
+ UnoPolyPolygon::UnoPolyPolygon( const B2DPolyPolygon& rPolyPoly ) :
+ UnoPolyPolygonBase( m_aMutex ),
+ maPolyPoly( rPolyPoly ),
+ meFillRule( rendering::FillRule_EVEN_ODD )
+ {
+ // or else races will haunt us.
+ maPolyPoly.makeUnique();
+ }
+
+ void SAL_CALL UnoPolyPolygon::addPolyPolygon(
+ const geometry::RealPoint2D& position,
+ const uno::Reference< rendering::XPolyPolygon2D >& polyPolygon )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ modifying();
+
+ // TODO(F1): Correctly fulfill the UNO API
+ // specification. This will probably result in a vector of
+ // poly-polygons to be stored in this object.
+
+ const sal_Int32 nPolys( polyPolygon->getNumberOfPolygons() );
+
+ if( !polyPolygon.is() || !nPolys )
+ {
+ // invalid or empty polygon - nothing to do.
+ return;
+ }
+
+ B2DPolyPolygon aSrcPoly;
+ const UnoPolyPolygon* pSrc( dynamic_cast< UnoPolyPolygon* >(polyPolygon.get()) );
+
+ // try to extract polygon data from interface. First,
+ // check whether it's the same implementation object,
+ // which we can tunnel then.
+ if( pSrc )
+ {
+ aSrcPoly = pSrc->getPolyPolygon();
+ }
+ else
+ {
+ // not a known implementation object - try data source
+ // interfaces
+ uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly(
+ polyPolygon,
+ uno::UNO_QUERY );
+
+ if( xBezierPoly.is() )
+ {
+ aSrcPoly = unotools::polyPolygonFromBezier2DSequenceSequence(
+ xBezierPoly->getBezierSegments( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ else
+ {
+ uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly(
+ polyPolygon,
+ uno::UNO_QUERY );
+
+ // no implementation class and no data provider
+ // found - contract violation.
+ if( !xLinePoly.is() )
+ throw lang::IllegalArgumentException(
+ "UnoPolyPolygon::addPolyPolygon(): Invalid input "
+ "poly-polygon, cannot retrieve vertex data",
+ static_cast<cppu::OWeakObject*>(this), 1);
+
+ aSrcPoly = unotools::polyPolygonFromPoint2DSequenceSequence(
+ xLinePoly->getPoints( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ }
+
+ const B2DRange aBounds( utils::getRange( aSrcPoly ) );
+ const B2DVector aOffset( unotools::b2DPointFromRealPoint2D( position ) -
+ aBounds.getMinimum() );
+
+ if( !aOffset.equalZero() )
+ {
+ const B2DHomMatrix aTranslate(utils::createTranslateB2DHomMatrix(aOffset));
+ aSrcPoly.transform( aTranslate );
+ }
+
+ maPolyPoly.append( aSrcPoly );
+ }
+
+ sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygons()
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ return maPolyPoly.count();
+ }
+
+ sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygonPoints(
+ sal_Int32 polygon )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( polygon );
+
+ return maPolyPoly.getB2DPolygon(polygon).count();
+ }
+
+ rendering::FillRule SAL_CALL UnoPolyPolygon::getFillRule()
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ return meFillRule;
+ }
+
+ void SAL_CALL UnoPolyPolygon::setFillRule(
+ rendering::FillRule fillRule )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ modifying();
+
+ meFillRule = fillRule;
+ }
+
+ sal_Bool SAL_CALL UnoPolyPolygon::isClosed(
+ sal_Int32 index )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( index );
+
+ return maPolyPoly.getB2DPolygon(index).isClosed();
+ }
+
+ void SAL_CALL UnoPolyPolygon::setClosed(
+ sal_Int32 index,
+ sal_Bool closedState )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ modifying();
+
+ if( index == -1 )
+ {
+ // set all
+ maPolyPoly.setClosed( closedState );
+ }
+ else
+ {
+ checkIndex( index );
+
+ // fetch referenced polygon, change state
+ B2DPolygon aTmp( maPolyPoly.getB2DPolygon(index) );
+ aTmp.setClosed( closedState );
+
+ // set back to container
+ maPolyPoly.setB2DPolygon( index, aTmp );
+ }
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > SAL_CALL UnoPolyPolygon::getPoints(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+
+ return unotools::pointSequenceSequenceFromB2DPolyPolygon(
+ getSubsetPolyPolygon( nPolygonIndex,
+ nNumberOfPolygons,
+ nPointIndex,
+ nNumberOfPoints ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setPoints(
+ const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points,
+ sal_Int32 nPolygonIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ modifying();
+
+ const B2DPolyPolygon& rNewPolyPoly(
+ unotools::polyPolygonFromPoint2DSequenceSequence( points ) );
+
+ if( nPolygonIndex == -1 )
+ {
+ maPolyPoly = rNewPolyPoly;
+ }
+ else
+ {
+ checkIndex( nPolygonIndex );
+
+ maPolyPoly.insert( nPolygonIndex, rNewPolyPoly );
+ }
+ }
+
+ geometry::RealPoint2D SAL_CALL UnoPolyPolygon::getPoint(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+
+ if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(rPoly.count()) )
+ throw lang::IndexOutOfBoundsException();
+
+ return unotools::point2DFromB2DPoint( rPoly.getB2DPoint( nPointIndex ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setPoint(
+ const geometry::RealPoint2D& point,
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+ modifying();
+
+ B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+
+ if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(aPoly.count()) )
+ throw lang::IndexOutOfBoundsException();
+
+ aPoly.setB2DPoint( nPointIndex,
+ unotools::b2DPointFromRealPoint2D( point ) );
+ maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly );
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > SAL_CALL UnoPolyPolygon::getBezierSegments(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ return unotools::bezierSequenceSequenceFromB2DPolyPolygon(
+ getSubsetPolyPolygon( nPolygonIndex,
+ nNumberOfPolygons,
+ nPointIndex,
+ nNumberOfPoints ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setBezierSegments(
+ const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points,
+ sal_Int32 nPolygonIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ modifying();
+ const B2DPolyPolygon& rNewPolyPoly(
+ unotools::polyPolygonFromBezier2DSequenceSequence( points ) );
+
+ if( nPolygonIndex == -1 )
+ {
+ maPolyPoly = rNewPolyPoly;
+ }
+ else
+ {
+ checkIndex( nPolygonIndex );
+
+ maPolyPoly.insert( nPolygonIndex, rNewPolyPoly );
+ }
+ }
+
+ geometry::RealBezierSegment2D SAL_CALL UnoPolyPolygon::getBezierSegment( sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+ const sal_uInt32 nPointCount(rPoly.count());
+
+ if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(nPointCount) )
+ throw lang::IndexOutOfBoundsException();
+
+ const B2DPoint& rPt( rPoly.getB2DPoint( nPointIndex ) );
+ const B2DPoint& rCtrl0( rPoly.getNextControlPoint(nPointIndex) );
+ const B2DPoint& rCtrl1( rPoly.getPrevControlPoint((nPointIndex + 1) % nPointCount) );
+
+ return geometry::RealBezierSegment2D( rPt.getX(),
+ rPt.getY(),
+ rCtrl0.getX(),
+ rCtrl0.getY(),
+ rCtrl1.getX(),
+ rCtrl1.getY() );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setBezierSegment( const geometry::RealBezierSegment2D& segment,
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+ modifying();
+
+ B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(nPointCount) )
+ throw lang::IndexOutOfBoundsException();
+
+ aPoly.setB2DPoint( nPointIndex,
+ B2DPoint( segment.Px,
+ segment.Py ) );
+ aPoly.setNextControlPoint(nPointIndex,
+ B2DPoint(segment.C1x, segment.C1y));
+ aPoly.setPrevControlPoint((nPointIndex + 1) % nPointCount,
+ B2DPoint(segment.C2x, segment.C2y));
+
+ maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly );
+ }
+
+ B2DPolyPolygon UnoPolyPolygon::getSubsetPolyPolygon(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints ) const
+ {
+ osl::MutexGuard const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const sal_Int32 nPolyCount( maPolyPoly.count() );
+
+ // check for "full polygon" case
+ if( !nPolygonIndex &&
+ !nPointIndex &&
+ nNumberOfPolygons == nPolyCount &&
+ nNumberOfPoints == -1 )
+ {
+ return maPolyPoly;
+ }
+
+ B2DPolyPolygon aSubsetPoly;
+
+ // create temporary polygon (as an extract from maPoly,
+ // which contains the requested subset)
+ for( sal_Int32 i=nPolygonIndex; i<nNumberOfPolygons; ++i )
+ {
+ checkIndex(i);
+
+ const B2DPolygon& rCurrPoly( maPolyPoly.getB2DPolygon(i) );
+
+ sal_Int32 nFirstPoint(0);
+ sal_Int32 nLastPoint(nPolyCount-1);
+
+ if( nPointIndex && i==nPolygonIndex )
+ {
+ // very first polygon - respect nPointIndex, if
+ // not zero
+
+ // empty polygon - impossible to specify _any_
+ // legal value except 0 here!
+ if( !nPolyCount)
+ throw lang::IndexOutOfBoundsException();
+
+ nFirstPoint = nPointIndex;
+ }
+
+ if( i==nNumberOfPolygons-1 && nNumberOfPoints != -1 )
+ {
+ // very last polygon - respect nNumberOfPoints
+
+ // empty polygon - impossible to specify _any_
+ // legal value except -1 here!
+ if( !nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ nLastPoint = nFirstPoint+nNumberOfPoints;
+ }
+
+ if( !nPolyCount )
+ {
+ // empty polygon - index checks already performed
+ // above, now simply append empty polygon
+ aSubsetPoly.append( rCurrPoly );
+ }
+ else
+ {
+ if( nFirstPoint < 0 || nFirstPoint >= nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ if( nLastPoint < 0 || nLastPoint >= nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ B2DPolygon aTmp;
+ for( sal_Int32 j=nFirstPoint; j<nLastPoint; ++j )
+ aTmp.append( rCurrPoly.getB2DPoint(j) );
+
+ aSubsetPoly.append( aTmp );
+ }
+ }
+
+ return aSubsetPoly;
+ }
+
+#define IMPLEMENTATION_NAME "gfx::internal::UnoPolyPolygon"
+#define SERVICE_NAME "com.sun.star.rendering.PolyPolygon2D"
+ OUString SAL_CALL UnoPolyPolygon::getImplementationName()
+ {
+ return IMPLEMENTATION_NAME;
+ }
+
+ sal_Bool SAL_CALL UnoPolyPolygon::supportsService( const OUString& ServiceName )
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ uno::Sequence< OUString > SAL_CALL UnoPolyPolygon::getSupportedServiceNames()
+ {
+ return { SERVICE_NAME };
+ }
+
+ B2DPolyPolygon UnoPolyPolygon::getPolyPolygon() const
+ {
+ osl::MutexGuard const guard( m_aMutex );
+
+ // detach result from us
+ B2DPolyPolygon aRet( maPolyPoly );
+ aRet.makeUnique();
+ return aRet;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/zoomtools.cxx b/basegfx/source/tools/zoomtools.cxx
new file mode 100644
index 000000000..488b19b31
--- /dev/null
+++ b/basegfx/source/tools/zoomtools.cxx
@@ -0,0 +1,113 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/zoomtools.hxx>
+
+namespace basegfx::zoomtools
+{
+
+/** 2^(1/6) as the default step
+
+ This ensures (unless the rounding is used) that 6 steps lead
+ to double / half zoom level.
+*/
+const double ZOOM_FACTOR = 1.12246205;
+
+/**
+* Round a value against a specified multiple. Values below half
+* of the multiple are rounded down and all others are rounded up.
+*
+* @param nCurrent current value
+* @param nMultiple multiple against which the current value is rounded
+*/
+static long roundMultiple(long nCurrent, int nMultiple)
+{
+ // round zoom to a multiple of nMultiple
+ return (( nCurrent + nMultiple / 2 ) - ( nCurrent + nMultiple / 2 ) % nMultiple);
+}
+
+/**
+* Convert geometric progression results into more common values by
+* rounding them against certain multiples depending on the size.
+* Beginning with 50 the multiple is 5, with 100, 10, and so on.
+*
+* @param nCurrent current zoom factor
+*/
+static long roundZoom(double nCurrent)
+{
+ // convert nCurrent properly to int
+ long nNew = nCurrent + 0.5;
+
+ // round to more common numbers above 50
+ if (nNew > 1000) {
+ nNew = roundMultiple(nNew, 100);
+ } else if ( nNew > 500 ) {
+ nNew = roundMultiple(nNew, 50);
+ } else if ( nNew > 100 ) {
+ nNew = roundMultiple(nNew, 10);
+ } else if ( nNew > 50 ) {
+ nNew = roundMultiple(nNew, 5);
+ }
+
+ return nNew;
+}
+
+/**
+* Make sure that a certain step isn't skipped during the zooming
+* progress.
+*
+* @param nCurrent current zoom factor
+* @param nPrevious previous zoom factor
+* @param nStep step which shouldn't be skipped
+*/
+static long enforceStep(long nCurrent, long nPrevious, int nStep)
+{
+ if ((( nCurrent > nStep ) && ( nPrevious < nStep ))
+ || (( nCurrent < nStep ) && ( nPrevious > nStep )))
+ return nStep;
+ else
+ return nCurrent;
+}
+
+/**
+* Increasing the zoom level.
+*
+* @param nCurrent current zoom factor
+*/
+long zoomIn(long nCurrent)
+{
+ long nNew = roundZoom( nCurrent * ZOOM_FACTOR );
+ // make sure some values are not skipped
+ nNew = enforceStep(nNew, nCurrent, 200);
+ nNew = enforceStep(nNew, nCurrent, 100);
+ nNew = enforceStep(nNew, nCurrent, 75);
+ nNew = enforceStep(nNew, nCurrent, 50);
+ nNew = enforceStep(nNew, nCurrent, 25);
+ return nNew;
+}
+
+/**
+* Decreasing the zoom level.
+*
+* @param nCurrent current zoom factor
+*/
+long zoomOut(long nCurrent)
+{
+ long nNew = roundZoom( nCurrent / ZOOM_FACTOR );
+ // make sure some values are not skipped
+ nNew = enforceStep(nNew, nCurrent, 200);
+ nNew = enforceStep(nNew, nCurrent, 100);
+ nNew = enforceStep(nNew, nCurrent, 75);
+ nNew = enforceStep(nNew, nCurrent, 50);
+ nNew = enforceStep(nNew, nCurrent, 25);
+ return nNew;
+}
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */