summaryrefslogtreecommitdiffstats
path: root/tools/source/generic
diff options
context:
space:
mode:
Diffstat (limited to 'tools/source/generic')
-rw-r--r--tools/source/generic/b3dtrans.cxx456
-rw-r--r--tools/source/generic/bigint.cxx852
-rw-r--r--tools/source/generic/color.cxx266
-rw-r--r--tools/source/generic/config.cxx944
-rw-r--r--tools/source/generic/fract.cxx537
-rw-r--r--tools/source/generic/gen.cxx225
-rw-r--r--tools/source/generic/line.cxx140
-rw-r--r--tools/source/generic/point.cxx86
-rw-r--r--tools/source/generic/poly.cxx1866
-rw-r--r--tools/source/generic/poly2.cxx520
-rw-r--r--tools/source/generic/svborder.cxx35
11 files changed, 5927 insertions, 0 deletions
diff --git a/tools/source/generic/b3dtrans.cxx b/tools/source/generic/b3dtrans.cxx
new file mode 100644
index 0000000000..0cae750da0
--- /dev/null
+++ b/tools/source/generic/b3dtrans.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 <tools/b3dtrans.hxx>
+
+#include <osl/diagnose.h>
+
+ // Near and far clipping planes
+constexpr double gfNearBound = 0.001;
+constexpr double gfFarBound = 1.001;
+
+
+// B3dTransformationSet --------------------------------------------------------
+// Transformations for all 3D output
+
+B3dTransformationSet::B3dTransformationSet()
+{
+ Reset();
+}
+
+B3dTransformationSet::~B3dTransformationSet()
+{
+}
+
+void B3dTransformationSet::Orientation(basegfx::B3DHomMatrix& rTarget, const basegfx::B3DPoint& aVRP, basegfx::B3DVector aVPN, basegfx::B3DVector aVUP)
+{
+ rTarget.translate( -aVRP.getX(), -aVRP.getY(), -aVRP.getZ());
+ aVUP.normalize();
+ aVPN.normalize();
+ basegfx::B3DVector aRx(aVUP);
+ basegfx::B3DVector aRy(aVPN);
+ aRx = aRx.getPerpendicular(aRy);
+ aRx.normalize();
+ aRy = aRy.getPerpendicular(aRx);
+ aRy.normalize();
+ basegfx::B3DHomMatrix aTemp;
+ aTemp.set(0, 0, aRx.getX());
+ aTemp.set(0, 1, aRx.getY());
+ aTemp.set(0, 2, aRx.getZ());
+ aTemp.set(1, 0, aRy.getX());
+ aTemp.set(1, 1, aRy.getY());
+ aTemp.set(1, 2, aRy.getZ());
+ aTemp.set(2, 0, aVPN.getX());
+ aTemp.set(2, 1, aVPN.getY());
+ aTemp.set(2, 2, aVPN.getZ());
+ rTarget *= aTemp;
+}
+
+void B3dTransformationSet::Frustum(basegfx::B3DHomMatrix& rTarget, double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar)
+{
+ if(!(fNear > 0.0))
+ {
+ fNear = 0.001;
+ }
+ if(!(fFar > 0.0))
+ {
+ fFar = 1.0;
+ }
+ if(fNear == fFar)
+ {
+ fFar = fNear + 1.0;
+ }
+ if(fLeft == fRight)
+ {
+ fLeft -= 1.0;
+ fRight += 1.0;
+ }
+ if(fTop == fBottom)
+ {
+ fBottom -= 1.0;
+ fTop += 1.0;
+ }
+ basegfx::B3DHomMatrix aTemp;
+
+ aTemp.set(0, 0, 2.0 * fNear / (fRight - fLeft));
+ aTemp.set(1, 1, 2.0 * fNear / (fTop - fBottom));
+ aTemp.set(0, 2, (fRight + fLeft) / (fRight - fLeft));
+ aTemp.set(1, 2, (fTop + fBottom) / (fTop - fBottom));
+ aTemp.set(2, 2, -1.0 * ((fFar + fNear) / (fFar - fNear)));
+ aTemp.set(3, 2, -1.0);
+ aTemp.set(2, 3, -1.0 * ((2.0 * fFar * fNear) / (fFar - fNear)));
+ aTemp.set(3, 3, 0.0);
+
+ rTarget *= aTemp;
+}
+
+void B3dTransformationSet::Ortho(basegfx::B3DHomMatrix& rTarget,
+ double fLeft, double fRight, double fBottom, double fTop,
+ double fNear, double fFar)
+{
+ if(fNear == fFar)
+ {
+ OSL_FAIL("Near and far clipping plane in Ortho definition are identical");
+ fFar = fNear + 1.0;
+ }
+ if(fLeft == fRight)
+ {
+ OSL_FAIL("Left and right in Ortho definition are identical");
+ fLeft -= 1.0;
+ fRight += 1.0;
+ }
+ if(fTop == fBottom)
+ {
+ OSL_FAIL("Top and bottom in Ortho definition are identical");
+ fBottom -= 1.0;
+ fTop += 1.0;
+ }
+ basegfx::B3DHomMatrix aTemp;
+
+ aTemp.set(0, 0, 2.0 / (fRight - fLeft));
+ aTemp.set(1, 1, 2.0 / (fTop - fBottom));
+ aTemp.set(2, 2, -1.0 * (2.0 / (fFar - fNear)));
+ aTemp.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft)));
+ aTemp.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom)));
+ aTemp.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear)));
+
+ rTarget *= aTemp;
+}
+
+/// reset values
+void B3dTransformationSet::Reset()
+{
+ // Reset matrices to identity matrices
+ maObjectTrans.identity();
+ PostSetObjectTrans();
+
+ Orientation(maOrientation);
+ PostSetOrientation();
+
+ maTexture.identity();
+
+ mfLeftBound = mfBottomBound = -1.0;
+ mfRightBound = mfTopBound = 1.0;
+
+ mfRatio = 0.0;
+
+ maViewportRectangle = tools::Rectangle(-1, -1, 2, 2);
+ maVisibleRectangle = maViewportRectangle;
+
+ mbPerspective = true;
+
+ mbProjectionValid = false;
+
+ CalcViewport();
+}
+
+/// Object transformation
+void B3dTransformationSet::PostSetObjectTrans()
+{
+ // Assign and compute inverse
+ maInvObjectTrans = maObjectTrans;
+ maInvObjectTrans.invert();
+}
+
+void B3dTransformationSet::SetOrientation(const basegfx::B3DPoint& rVRP, const basegfx::B3DVector& rVPN, const basegfx::B3DVector& rVUP)
+{
+ maOrientation.identity();
+ Orientation(maOrientation, rVRP, rVPN, rVUP);
+
+ PostSetOrientation();
+}
+
+void B3dTransformationSet::PostSetOrientation()
+{
+ // Assign and compute inverse
+ maInvOrientation = maOrientation;
+ maInvOrientation.invert();
+}
+
+/// Projections for transformations
+void B3dTransformationSet::SetProjection(const basegfx::B3DHomMatrix& mProject)
+{
+ maProjection = mProject;
+ PostSetProjection();
+}
+
+const basegfx::B3DHomMatrix& B3dTransformationSet::GetProjection()
+{
+ if(!mbProjectionValid)
+ CalcViewport();
+ return maProjection;
+}
+
+void B3dTransformationSet::PostSetProjection()
+{
+ // Assign and compute inverse
+ maInvProjection = GetProjection();
+ maInvProjection.invert();
+}
+
+/// Transformations for viewport
+void B3dTransformationSet::CalcViewport()
+{
+ // Parameters for projection
+ double fLeft(mfLeftBound);
+ double fRight(mfRightBound);
+ double fBottom(mfBottomBound);
+ double fTop(mfTopBound);
+
+ // Adjust projection to aspect ratio, if set
+ if(GetRatio() != 0.0)
+ {
+ // Compute current aspect ratio of boundaries
+ double fBoundWidth = static_cast<double>(maViewportRectangle.GetWidth() + 1);
+ double fBoundHeight = static_cast<double>(maViewportRectangle.GetHeight() + 1);
+ double fActRatio = 1;
+ double fFactor;
+
+ if(fBoundWidth != 0.0)
+ fActRatio = fBoundHeight / fBoundWidth;
+ // FIXME else in this case has a lot of problems, should this return.
+
+ // scale down larger part
+ if(fActRatio > mfRatio)
+ {
+ // scale down Y
+ fFactor = fActRatio;
+ fTop *= fFactor;
+ fBottom *= fFactor;
+ }
+ else
+ {
+ // scale down X
+ fFactor = 1.0 / fActRatio;
+ fRight *= fFactor;
+ fLeft *= fFactor;
+ }
+ }
+
+ // Do projection and object areas overlap?
+ maSetBound = maViewportRectangle;
+
+ // Reset projection with new values
+ basegfx::B3DHomMatrix aNewProjection;
+
+ // #i36281#
+ // OpenGL needs a little more rough additional size to not let
+ // the front face vanish. Changed from SMALL_DVALUE to 0.000001,
+ // which is 1/10000th, compared with 1/tenth of a million from SMALL_DVALUE.
+ const double fDistPart((gfFarBound - gfNearBound) * 0.0001);
+
+ // To avoid critical clipping, set Near & Far generously
+ if(mbPerspective)
+ {
+ Frustum(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart);
+ }
+ else
+ {
+ Ortho(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart);
+ }
+
+ // Set to true to guarantee loop termination
+ mbProjectionValid = true;
+
+ // set new projection
+ SetProjection(aNewProjection);
+
+ // fill parameters for ViewportTransformation
+ // Translation
+ maTranslate.setX(static_cast<double>(maSetBound.Left()) + ((maSetBound.GetWidth() - 1) / 2.0));
+ maTranslate.setY(static_cast<double>(maSetBound.Top()) + ((maSetBound.GetHeight() - 1) / 2.0));
+ maTranslate.setZ(ZBUFFER_DEPTH_RANGE / 2.0);
+
+ // Scaling
+ maScale.setX((maSetBound.GetWidth() - 1) / 2.0);
+ maScale.setY((maSetBound.GetHeight() - 1) / -2.0);
+ maScale.setZ(ZBUFFER_DEPTH_RANGE / 2.0);
+}
+
+void B3dTransformationSet::SetRatio(double fNew)
+{
+ if(mfRatio != fNew)
+ {
+ mfRatio = fNew;
+ mbProjectionValid = false;
+ }
+}
+
+void B3dTransformationSet::SetDeviceRectangle(double fL, double fR, double fB, double fT)
+{
+ if(fL != mfLeftBound || fR != mfRightBound || fB != mfBottomBound || fT != mfTopBound)
+ {
+ mfLeftBound = fL;
+ mfRightBound = fR;
+ mfBottomBound = fB;
+ mfTopBound = fT;
+
+ mbProjectionValid = false;
+
+ // Broadcast changes
+ DeviceRectangleChange();
+ }
+}
+
+void B3dTransformationSet::DeviceRectangleChange()
+{
+}
+
+void B3dTransformationSet::SetPerspective(bool bNew)
+{
+ if(mbPerspective != bNew)
+ {
+ mbPerspective = bNew;
+ mbProjectionValid = false;
+ }
+}
+
+void B3dTransformationSet::SetViewportRectangle(tools::Rectangle const & rRect, tools::Rectangle const & rVisible)
+{
+ if(rRect != maViewportRectangle || rVisible != maVisibleRectangle)
+ {
+ maViewportRectangle = rRect;
+ maVisibleRectangle = rVisible;
+
+ mbProjectionValid = false;
+ }
+}
+
+// direct access to various transformations
+
+basegfx::B3DPoint B3dTransformationSet::WorldToEyeCoor(const basegfx::B3DPoint& rVec)
+{
+ basegfx::B3DPoint aVec(rVec);
+ aVec *= maOrientation;
+ return aVec;
+}
+
+basegfx::B3DPoint B3dTransformationSet::EyeToWorldCoor(const basegfx::B3DPoint& rVec)
+{
+ basegfx::B3DPoint aVec(rVec);
+ aVec *= maInvOrientation;
+ return aVec;
+}
+
+// B3dViewport -----------------------------------------------------------------
+
+B3dViewport::B3dViewport()
+: aVRP(0, 0, 0),
+ aVPN(0, 0, 1),
+ aVUV(0, 1, 0)
+{
+ CalcOrientation();
+}
+
+B3dViewport::~B3dViewport()
+{
+}
+
+void B3dViewport::SetVUV(const basegfx::B3DVector& rNewVUV)
+{
+ aVUV = rNewVUV;
+ CalcOrientation();
+}
+
+void B3dViewport::SetViewportValues(
+ const basegfx::B3DPoint& rNewVRP,
+ const basegfx::B3DVector& rNewVPN,
+ const basegfx::B3DVector& rNewVUV)
+{
+ aVRP = rNewVRP;
+ aVPN = rNewVPN;
+ aVUV = rNewVUV;
+ CalcOrientation();
+}
+
+void B3dViewport::CalcOrientation()
+{
+ SetOrientation(aVRP, aVPN, aVUV);
+}
+
+// B3dCamera -------------------------------------------------------------------
+
+B3dCamera::B3dCamera(
+ const basegfx::B3DPoint& rPos, const basegfx::B3DVector& rLkAt,
+ double fFocLen, double fBnkAng)
+: aPosition(rPos),
+ aLookAt(rLkAt),
+ fFocalLength(fFocLen),
+ fBankAngle(fBnkAng)
+{
+ CalcNewViewportValues();
+}
+
+B3dCamera::~B3dCamera()
+{
+}
+
+void B3dCamera::DeviceRectangleChange()
+{
+ // call parent
+ B3dViewport::DeviceRectangleChange();
+
+ // react to changes
+ CalcNewViewportValues();
+}
+
+void B3dCamera::CalcNewViewportValues()
+{
+ basegfx::B3DVector aNewVPN(aPosition - aLookAt);
+
+ basegfx::B3DVector aNewVUV(0.0, 1.0, 0.0);
+ if(aNewVPN.getLength() < aNewVPN.getY())
+ aNewVUV.setX(0.5);
+
+ aNewVUV.normalize();
+ aNewVPN.normalize();
+
+ basegfx::B3DVector aNewToTheRight = aNewVPN.getPerpendicular(aNewVUV);
+ aNewToTheRight.normalize();
+ aNewVUV = aNewToTheRight.getPerpendicular(aNewVPN);
+ aNewVUV.normalize();
+
+ SetViewportValues(aPosition, aNewVPN, aNewVUV);
+ CalcFocalLength();
+
+ if(fBankAngle != 0.0)
+ {
+ basegfx::B3DHomMatrix aRotMat;
+ aRotMat.rotate(0.0, 0.0, fBankAngle);
+ basegfx::B3DVector aUp(0.0, 1.0, 0.0);
+ aUp *= aRotMat;
+ aUp = EyeToWorldCoor(aUp);
+ aUp.normalize();
+ SetVUV(aUp);
+ }
+}
+
+void B3dCamera::CalcFocalLength()
+{
+ double fWidth = GetDeviceRectangleWidth();
+
+ // Adjust focal length based on given position
+ basegfx::B3DPoint aOldPosition = WorldToEyeCoor({});
+ if(fWidth != 0.0)
+ fFocalLength = aOldPosition.getZ() / fWidth * 35.0;
+ if(fFocalLength < 5.0)
+ fFocalLength = 5.0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/bigint.cxx b/tools/source/generic/bigint.cxx
new file mode 100644
index 0000000000..51810cab17
--- /dev/null
+++ b/tools/source/generic/bigint.cxx
@@ -0,0 +1,852 @@
+/* -*- 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 <math.h>
+
+#include <osl/diagnose.h>
+#include <tools/bigint.hxx>
+
+#include <algorithm>
+#include <string.h>
+
+/**
+ * The range in which we can perform add/sub without fear of overflow
+ */
+const sal_Int32 MY_MAXLONG = 0x3fffffff;
+const sal_Int32 MY_MINLONG = -MY_MAXLONG;
+
+/*
+ * The algorithms for Addition, Subtraction, Multiplication and Division
+ * of large numbers originate from SEMINUMERICAL ALGORITHMS by
+ * DONALD E. KNUTH in the series The Art of Computer Programming:
+ * chapter 4.3.1. The Classical Algorithms.
+ */
+
+// TODO: Needs conversion to sal_uInt16/INT16/sal_uInt32/sal_Int32
+void BigInt::MakeBigInt( const BigInt& rVal )
+{
+ if ( rVal.nLen != 0 )
+ {
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rVal), sizeof( BigInt ) );
+ while ( nLen > 1 && nNum[nLen-1] == 0 )
+ nLen--;
+ }
+ else
+ {
+ nVal = rVal.nVal;
+ sal_uInt32 nTmp;
+ if (nVal < 0)
+ {
+ bIsNeg = true;
+ nTmp = -static_cast<sal_Int64>(nVal);
+ }
+ else
+ {
+ bIsNeg = false;
+ nTmp = nVal;
+ }
+
+ nNum[0] = static_cast<sal_uInt16>(nTmp & 0xffffL);
+ nNum[1] = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nTmp & 0xffff0000L )
+ nLen = 2;
+ else
+ nLen = 1;
+ }
+}
+
+void BigInt::Normalize()
+{
+ if ( nLen != 0 )
+ {
+ while ( nLen > 1 && nNum[nLen-1] == 0 )
+ nLen--;
+
+ if ( nLen < 3 )
+ {
+ sal_Int32 newVal;
+ if ( nLen < 2 )
+ newVal = nNum[0];
+ else if ( nNum[1] & 0x8000 )
+ return;
+ else
+ newVal = (static_cast<sal_Int32>(nNum[1]) << 16) + nNum[0];
+
+ nLen = 0;
+ nVal = newVal;
+
+ if ( bIsNeg )
+ nVal = -nVal;
+ }
+ // else nVal is undefined !!! W.P.
+ }
+ // why? nVal is undefined ??? W.P.
+ else if ( nVal & 0xFFFF0000L )
+ nLen = 2;
+ else
+ nLen = 1;
+}
+
+void BigInt::Mult( const BigInt &rVal, sal_uInt16 nMul )
+{
+ sal_uInt16 nK = 0;
+ for ( int i = 0; i < rVal.nLen; i++ )
+ {
+ sal_uInt32 nTmp = static_cast<sal_uInt32>(rVal.nNum[i]) * static_cast<sal_uInt32>(nMul) + nK;
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ nNum[i] = static_cast<sal_uInt16>(nTmp);
+ }
+
+ if ( nK )
+ {
+ nNum[rVal.nLen] = nK;
+ nLen = rVal.nLen + 1;
+ }
+ else
+ nLen = rVal.nLen;
+
+ bIsNeg = rVal.bIsNeg;
+}
+
+void BigInt::Div( sal_uInt16 nDiv, sal_uInt16& rRem )
+{
+ sal_uInt32 nK = 0;
+ for ( int i = nLen - 1; i >= 0; i-- )
+ {
+ sal_uInt32 nTmp = static_cast<sal_uInt32>(nNum[i]) + (nK << 16);
+ nNum[i] = static_cast<sal_uInt16>(nTmp / nDiv);
+ nK = nTmp % nDiv;
+ }
+ rRem = static_cast<sal_uInt16>(nK);
+
+ if ( nNum[nLen-1] == 0 )
+ nLen -= 1;
+}
+
+bool BigInt::IsLess( const BigInt& rVal ) const
+{
+ if ( rVal.nLen < nLen)
+ return true;
+ if ( rVal.nLen > nLen )
+ return false;
+
+ int i;
+ for ( i = nLen - 1; i > 0 && nNum[i] == rVal.nNum[i]; i-- )
+ {
+ }
+ return rVal.nNum[i] < nNum[i];
+}
+
+void BigInt::AddLong( BigInt& rB, BigInt& rErg )
+{
+ if ( bIsNeg == rB.bIsNeg )
+ {
+ int i;
+ char len;
+
+ // if length of the two values differ, fill remaining positions
+ // of the smaller value with zeros.
+ if (nLen >= rB.nLen)
+ {
+ len = nLen;
+ for (i = rB.nLen; i < len; i++)
+ rB.nNum[i] = 0;
+ }
+ else
+ {
+ len = rB.nLen;
+ for (i = nLen; i < len; i++)
+ nNum[i] = 0;
+ }
+
+ // Add numerals, starting from the back
+ sal_Int32 k;
+ sal_Int32 nZ = 0;
+ for (i = 0, k = 0; i < len; i++) {
+ nZ = static_cast<sal_Int32>(nNum[i]) + static_cast<sal_Int32>(rB.nNum[i]) + k;
+ if (nZ & 0xff0000L)
+ k = 1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ // If an overflow occurred, add to solution
+ if (nZ & 0xff0000L) // or if(k)
+ {
+ rErg.nNum[i] = 1;
+ len++;
+ }
+ // Set length and sign
+ rErg.nLen = len;
+ rErg.bIsNeg = bIsNeg && rB.bIsNeg;
+ }
+ // If one of the values is negative, perform subtraction instead
+ else if (bIsNeg)
+ {
+ bIsNeg = false;
+ rB.SubLong(*this, rErg);
+ bIsNeg = true;
+ }
+ else
+ {
+ rB.bIsNeg = false;
+ SubLong(rB, rErg);
+ rB.bIsNeg = true;
+ }
+}
+
+void BigInt::SubLong( BigInt& rB, BigInt& rErg )
+{
+ if ( bIsNeg == rB.bIsNeg )
+ {
+ int i;
+ char len;
+ sal_Int32 nZ, k;
+
+ // if length of the two values differ, fill remaining positions
+ // of the smaller value with zeros.
+ if (nLen >= rB.nLen)
+ {
+ len = nLen;
+ for (i = rB.nLen; i < len; i++)
+ rB.nNum[i] = 0;
+ }
+ else
+ {
+ len = rB.nLen;
+ for (i = nLen; i < len; i++)
+ nNum[i] = 0;
+ }
+
+ if ( IsLess(rB) )
+ {
+ for (i = 0, k = 0; i < len; i++)
+ {
+ nZ = static_cast<sal_Int32>(nNum[i]) - static_cast<sal_Int32>(rB.nNum[i]) + k;
+ if (nZ < 0)
+ k = -1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ rErg.bIsNeg = bIsNeg;
+ }
+ else
+ {
+ for (i = 0, k = 0; i < len; i++)
+ {
+ nZ = static_cast<sal_Int32>(rB.nNum[i]) - static_cast<sal_Int32>(nNum[i]) + k;
+ if (nZ < 0)
+ k = -1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ // if a < b, revert sign
+ rErg.bIsNeg = !bIsNeg;
+ }
+ rErg.nLen = len;
+ }
+ // If one of the values is negative, perform addition instead
+ else if (bIsNeg)
+ {
+ bIsNeg = false;
+ AddLong(rB, rErg);
+ bIsNeg = true;
+ rErg.bIsNeg = true;
+ }
+ else
+ {
+ rB.bIsNeg = false;
+ AddLong(rB, rErg);
+ rB.bIsNeg = true;
+ rErg.bIsNeg = false;
+ }
+}
+
+void BigInt::MultLong( const BigInt& rB, BigInt& rErg ) const
+{
+ int i, j;
+ sal_uInt32 nZ, k;
+
+ rErg.bIsNeg = bIsNeg != rB.bIsNeg;
+ rErg.nLen = nLen + rB.nLen;
+
+ for (i = 0; i < rErg.nLen; i++)
+ rErg.nNum[i] = 0;
+
+ for (j = 0; j < rB.nLen; j++)
+ {
+ for (i = 0, k = 0; i < nLen; i++)
+ {
+ nZ = static_cast<sal_uInt32>(nNum[i]) * static_cast<sal_uInt32>(rB.nNum[j]) +
+ static_cast<sal_uInt32>(rErg.nNum[i + j]) + k;
+ rErg.nNum[i + j] = static_cast<sal_uInt16>(nZ & 0xffffU);
+ k = nZ >> 16;
+ }
+ rErg.nNum[i + j] = static_cast<sal_uInt16>(k);
+ }
+}
+
+void BigInt::DivLong( const BigInt& rB, BigInt& rErg ) const
+{
+ int i, j;
+ sal_uInt16 nK, nQ, nMult;
+ sal_uInt16 nLenB = rB.nLen;
+ sal_uInt16 nLenB1 = rB.nLen - 1;
+ BigInt aTmpA, aTmpB;
+
+ nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1));
+
+ aTmpA.Mult( *this, nMult );
+ if ( aTmpA.nLen == nLen )
+ {
+ aTmpA.nNum[aTmpA.nLen] = 0;
+ aTmpA.nLen++;
+ }
+
+ aTmpB.Mult( rB, nMult );
+
+ for (j = aTmpA.nLen - 1; j >= nLenB; j--)
+ { // guess divisor
+ sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1];
+ if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1])
+ nQ = 0xFFFF;
+ else
+ nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]);
+
+ if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) >
+ ((nTmp - static_cast<sal_uInt32>(aTmpB.nNum[nLenB1]) * nQ) << 16) + aTmpA.nNum[j - 2])
+ nQ--;
+ // Start division
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i])
+ - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ)
+ - nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp);
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nK )
+ nK = static_cast<sal_uInt16>(0x10000U - nK);
+ }
+ sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] );
+ rNum -= nK;
+ if (aTmpA.nNum[j - nLenB + i] == 0)
+ rErg.nNum[j - nLenB] = nQ;
+ else
+ {
+ rErg.nNum[j - nLenB] = nQ - 1;
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL);
+ if (nTmp & 0xFFFF0000L)
+ nK = 1;
+ else
+ nK = 0;
+ }
+ }
+ }
+
+ rErg.bIsNeg = bIsNeg != rB.bIsNeg;
+ rErg.nLen = nLen - rB.nLen + 1;
+}
+
+void BigInt::ModLong( const BigInt& rB, BigInt& rErg ) const
+{
+ sal_uInt16 i, j;
+ sal_uInt16 nK, nQ, nMult;
+ sal_Int16 nLenB = rB.nLen;
+ sal_Int16 nLenB1 = rB.nLen - 1;
+ BigInt aTmpA, aTmpB;
+
+ nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1));
+
+ aTmpA.Mult( *this, nMult);
+ if ( aTmpA.nLen == nLen )
+ {
+ aTmpA.nNum[aTmpA.nLen] = 0;
+ aTmpA.nLen++;
+ }
+
+ aTmpB.Mult( rB, nMult);
+
+ for (j = aTmpA.nLen - 1; j >= nLenB; j--)
+ { // Guess divisor
+ sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1];
+ if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1])
+ nQ = 0xFFFF;
+ else
+ nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]);
+
+ if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) >
+ ((nTmp - aTmpB.nNum[nLenB1] * nQ) << 16) + aTmpA.nNum[j - 2])
+ nQ--;
+ // Start division
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i])
+ - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ)
+ - nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp);
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nK )
+ nK = static_cast<sal_uInt16>(0x10000U - nK);
+ }
+ sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] );
+ rNum = rNum - nK;
+ if (aTmpA.nNum[j - nLenB + i] == 0)
+ rErg.nNum[j - nLenB] = nQ;
+ else
+ {
+ rErg.nNum[j - nLenB] = nQ - 1;
+ nK = 0;
+ for (i = 0; i < nLenB; i++) {
+ nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL);
+ if (nTmp & 0xFFFF0000L)
+ nK = 1;
+ else
+ nK = 0;
+ }
+ }
+ }
+
+ rErg = aTmpA;
+ rErg.Div( nMult, nQ );
+}
+
+bool BigInt::ABS_IsLess( const BigInt& rB ) const
+{
+ if (nLen != 0 || rB.nLen != 0)
+ {
+ BigInt nA, nB;
+ nA.MakeBigInt( *this );
+ nB.MakeBigInt( rB );
+ if (nA.nLen == nB.nLen)
+ {
+ int i;
+ for (i = nA.nLen - 1; i > 0 && nA.nNum[i] == nB.nNum[i]; i--)
+ {
+ }
+ return nA.nNum[i] < nB.nNum[i];
+ }
+ else
+ return nA.nLen < nB.nLen;
+ }
+ if ( nVal < 0 )
+ if ( rB.nVal < 0 )
+ return nVal > rB.nVal;
+ else
+ return nVal > -rB.nVal;
+ else
+ if ( rB.nVal < 0 )
+ return nVal < -rB.nVal;
+ else
+ return nVal < rB.nVal;
+}
+
+BigInt::BigInt( const BigInt& rBigInt )
+ : nLen(0)
+ , bIsNeg(false)
+{
+ if ( rBigInt.nLen != 0 )
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) );
+ else
+ nVal = rBigInt.nVal;
+}
+
+BigInt::BigInt( std::u16string_view rString )
+ : nLen(0)
+{
+ bIsNeg = false;
+ nVal = 0;
+
+ bool bNeg = false;
+ auto p = rString.begin();
+ auto pEnd = rString.end();
+ if (p == pEnd)
+ return;
+ if ( *p == '-' )
+ {
+ bNeg = true;
+ p++;
+ }
+ if (p == pEnd)
+ return;
+ while( p != pEnd && *p >= '0' && *p <= '9' )
+ {
+ *this *= 10;
+ *this += *p - '0';
+ p++;
+ }
+ if ( nLen != 0 )
+ bIsNeg = bNeg;
+ else if( bNeg )
+ nVal = -nVal;
+}
+
+BigInt::BigInt( double nValue )
+ : nVal(0)
+{
+ if ( nValue < 0 )
+ {
+ nValue *= -1;
+ bIsNeg = true;
+ }
+ else
+ {
+ bIsNeg = false;
+ }
+
+ if ( nValue < 1 )
+ {
+ nVal = 0;
+ nLen = 0;
+ }
+ else
+ {
+ int i=0;
+
+ while ( ( nValue > 65536.0 ) && ( i < MAX_DIGITS ) )
+ {
+ nNum[i] = static_cast<sal_uInt16>(fmod( nValue, 65536.0 ));
+ nValue -= nNum[i];
+ nValue /= 65536.0;
+ i++;
+ }
+ if ( i < MAX_DIGITS )
+ nNum[i++] = static_cast<sal_uInt16>(nValue);
+
+ nLen = i;
+
+ if ( i < 3 )
+ Normalize();
+ }
+}
+
+BigInt::BigInt( sal_uInt32 nValue )
+ : nVal(0)
+{
+ if ( nValue & 0x80000000U )
+ {
+ bIsNeg = false;
+ nNum[0] = static_cast<sal_uInt16>(nValue & 0xffffU);
+ nNum[1] = static_cast<sal_uInt16>(nValue >> 16);
+ nLen = 2;
+ }
+ else
+ {
+ bIsNeg = false;
+ nVal = nValue;
+ nLen = 0;
+ }
+}
+
+BigInt::BigInt( sal_Int64 nValue )
+ : nVal(0)
+{
+ bIsNeg = nValue < 0;
+ nLen = 0;
+
+ if ((nValue >= SAL_MIN_INT32) && (nValue <= SAL_MAX_INT32))
+ {
+ nVal = static_cast<sal_Int32>(nValue);
+ }
+ else
+ {
+ sal_uInt64 nUValue = static_cast<sal_uInt64>(bIsNeg ? -nValue : nValue);
+ for (int i = 0; (i != sizeof(sal_uInt64) / 2) && (nUValue != 0); ++i)
+ {
+ nNum[i] = static_cast<sal_uInt16>(nUValue & 0xffffUL);
+ nUValue = nUValue >> 16;
+ ++nLen;
+ }
+ }
+}
+
+BigInt::operator double() const
+{
+ if ( nLen == 0 )
+ return static_cast<double>(nVal);
+ else
+ {
+ int i = nLen-1;
+ double nRet = static_cast<double>(static_cast<sal_uInt32>(nNum[i]));
+
+ while ( i )
+ {
+ nRet *= 65536.0;
+ i--;
+ nRet += static_cast<double>(static_cast<sal_uInt32>(nNum[i]));
+ }
+
+ if ( bIsNeg )
+ nRet *= -1;
+
+ return nRet;
+ }
+}
+
+BigInt& BigInt::operator=( const BigInt& rBigInt )
+{
+ if (this == &rBigInt)
+ return *this;
+
+ if ( rBigInt.nLen != 0 )
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) );
+ else
+ {
+ nLen = 0;
+ nVal = rBigInt.nVal;
+ }
+ return *this;
+}
+
+BigInt& BigInt::operator+=( const BigInt& rVal )
+{
+ if ( nLen == 0 && rVal.nLen == 0 )
+ {
+ if( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG
+ && nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG )
+ { // No overflows may occur here
+ nVal += rVal.nVal;
+ return *this;
+ }
+
+ if( (nVal < 0) != (rVal.nVal < 0) )
+ { // No overflows may occur here
+ nVal += rVal.nVal;
+ return *this;
+ }
+ }
+
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.AddLong( aTmp2, *this );
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator-=( const BigInt& rVal )
+{
+ if ( nLen == 0 && rVal.nLen == 0 )
+ {
+ if ( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG &&
+ nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG )
+ { // No overflows may occur here
+ nVal -= rVal.nVal;
+ return *this;
+ }
+
+ if ( (nVal < 0) == (rVal.nVal < 0) )
+ { // No overflows may occur here
+ nVal -= rVal.nVal;
+ return *this;
+ }
+ }
+
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.SubLong( aTmp2, *this );
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator*=( const BigInt& rVal )
+{
+ static const sal_Int32 MY_MAXSHORT = 0x00007fff;
+ static const sal_Int32 MY_MINSHORT = -MY_MAXSHORT;
+
+ if ( nLen == 0 && rVal.nLen == 0
+ && nVal <= MY_MAXSHORT && rVal.nVal <= MY_MAXSHORT
+ && nVal >= MY_MINSHORT && rVal.nVal >= MY_MINSHORT )
+ // TODO: not optimal !!! W.P.
+ { // No overflows may occur here
+ nVal *= rVal.nVal;
+ }
+ else
+ {
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( rVal );
+ aTmp2.MakeBigInt( *this );
+ aTmp1.MultLong(aTmp2, *this);
+ Normalize();
+ }
+ return *this;
+}
+
+BigInt& BigInt::operator/=( const BigInt& rVal )
+{
+ if ( rVal.nLen == 0 )
+ {
+ if ( rVal.nVal == 0 )
+ {
+ OSL_FAIL( "BigInt::operator/ --> divide by zero" );
+ return *this;
+ }
+
+ if ( nLen == 0 )
+ {
+ // No overflows may occur here
+ nVal /= rVal.nVal;
+ return *this;
+ }
+
+ if ( rVal.nVal == 1 )
+ return *this;
+
+ if ( rVal.nVal == -1 )
+ {
+ bIsNeg = !bIsNeg;
+ return *this;
+ }
+
+ if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF )
+ {
+ // Divide BigInt with an sal_uInt16
+ sal_uInt16 nTmp;
+ if ( rVal.nVal < 0 )
+ {
+ nTmp = static_cast<sal_uInt16>(-rVal.nVal);
+ bIsNeg = !bIsNeg;
+ }
+ else
+ nTmp = static_cast<sal_uInt16>(rVal.nVal);
+
+ Div( nTmp, nTmp );
+ Normalize();
+ return *this;
+ }
+ }
+
+ if ( ABS_IsLess( rVal ) )
+ {
+ *this = BigInt( 0 );
+ return *this;
+ }
+
+ // Divide BigInt with BigInt
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.DivLong(aTmp2, *this);
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator%=( const BigInt& rVal )
+{
+ if ( rVal.nLen == 0 )
+ {
+ if ( rVal.nVal == 0 )
+ {
+ OSL_FAIL( "BigInt::operator/ --> divide by zero" );
+ return *this;
+ }
+
+ if ( nLen == 0 )
+ {
+ // No overflows may occur here
+ nVal %= rVal.nVal;
+ return *this;
+ }
+
+ if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF )
+ {
+ // Divide Bigint by int16
+ sal_uInt16 nTmp;
+ if ( rVal.nVal < 0 )
+ {
+ nTmp = static_cast<sal_uInt16>(-rVal.nVal);
+ bIsNeg = !bIsNeg;
+ }
+ else
+ nTmp = static_cast<sal_uInt16>(rVal.nVal);
+
+ Div( nTmp, nTmp );
+ *this = BigInt( nTmp );
+ return *this;
+ }
+ }
+
+ if ( ABS_IsLess( rVal ) )
+ return *this;
+
+ // Divide BigInt with BigInt
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.ModLong(aTmp2, *this);
+ Normalize();
+ return *this;
+}
+
+bool operator==( const BigInt& rVal1, const BigInt& rVal2 )
+{
+ if (rVal1.nLen == 0 && rVal2.nLen == 0)
+ return rVal1.nVal == rVal2.nVal;
+
+ BigInt nA, nB;
+ nA.MakeBigInt(rVal1);
+ nB.MakeBigInt(rVal2);
+ return nA.bIsNeg == nB.bIsNeg && nA.nLen == nB.nLen
+ && std::equal(nA.nNum, nA.nNum + nA.nLen, nB.nNum);
+}
+
+bool operator<( const BigInt& rVal1, const BigInt& rVal2 )
+{
+ if (rVal1.nLen == 0 && rVal2.nLen == 0)
+ return rVal1.nVal < rVal2.nVal;
+
+ BigInt nA, nB;
+ nA.MakeBigInt(rVal1);
+ nB.MakeBigInt(rVal2);
+ if (nA.bIsNeg != nB.bIsNeg)
+ return !nB.bIsNeg;
+ if (nA.nLen != nB.nLen)
+ return nA.bIsNeg ? (nA.nLen > nB.nLen) : (nA.nLen < nB.nLen);
+ int i = nA.nLen - 1;
+ while (i > 0 && nA.nNum[i] == nB.nNum[i])
+ --i;
+ return nA.bIsNeg ? (nA.nNum[i] > nB.nNum[i]) : (nA.nNum[i] < nB.nNum[i]);
+}
+
+tools::Long BigInt::Scale( tools::Long nVal, tools::Long nMul, tools::Long nDiv )
+{
+ BigInt aVal( nVal );
+
+ aVal *= nMul;
+
+ if ( aVal.IsNeg() != ( nDiv < 0 ) )
+ aVal -= nDiv / 2; // for correct rounding
+ else
+ aVal += nDiv / 2; // for correct rounding
+
+ aVal /= nDiv;
+
+ return tools::Long( aVal );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/color.cxx b/tools/source/generic/color.cxx
new file mode 100644
index 0000000000..421045b588
--- /dev/null
+++ b/tools/source/generic/color.cxx
@@ -0,0 +1,266 @@
+/* -*- 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 <sal/config.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include <stdlib.h>
+
+#include <tools/color.hxx>
+#include <tools/helpers.hxx>
+#include <tools/long.hxx>
+#include <o3tl/string_view.hxx>
+#include <basegfx/color/bcolortools.hxx>
+
+void Color::IncreaseLuminance(sal_uInt8 cLumInc)
+{
+ R = sal_uInt8(std::clamp(R + cLumInc, 0, 255));
+ G = sal_uInt8(std::clamp(G + cLumInc, 0, 255));
+ B = sal_uInt8(std::clamp(B + cLumInc, 0, 255));
+}
+
+void Color::DecreaseLuminance(sal_uInt8 cLumDec)
+{
+ R = sal_uInt8(std::clamp(R - cLumDec, 0, 255));
+ G = sal_uInt8(std::clamp(G - cLumDec, 0, 255));
+ B = sal_uInt8(std::clamp(B - cLumDec, 0, 255));
+}
+
+void Color::DecreaseContrast(sal_uInt8 nContDec)
+{
+ if (nContDec)
+ {
+ const double fM = (128.0 - 0.4985 * nContDec) / 128.0;
+ const double fOff = 128.0 - fM * 128.0;
+
+ R = sal_uInt8(std::clamp(FRound(R * fM + fOff), tools::Long(0), tools::Long(255)));
+ G = sal_uInt8(std::clamp(FRound(G * fM + fOff), tools::Long(0), tools::Long(255)));
+ B = sal_uInt8(std::clamp(FRound(B * fM + fOff), tools::Long(0), tools::Long(255)));
+ }
+}
+
+// color space conversion
+
+void Color::RGBtoHSB( sal_uInt16& nHue, sal_uInt16& nSat, sal_uInt16& nBri ) const
+{
+ sal_uInt8 c[3];
+ sal_uInt8 cMax, cMin;
+
+ c[0] = R;
+ c[1] = G;
+ c[2] = B;
+
+ cMax = c[0];
+ if( c[1] > cMax )
+ cMax = c[1];
+ if( c[2] > cMax )
+ cMax = c[2];
+
+ // Brightness = max(R, G, B);
+ nBri = cMax * 100 / 255;
+
+ cMin = c[0];
+ if( c[1] < cMin )
+ cMin = c[1];
+ if( c[2] < cMin )
+ cMin = c[2];
+
+ sal_uInt8 cDelta = cMax - cMin;
+
+ // Saturation = max - min / max
+ if( nBri > 0 )
+ nSat = cDelta * 100 / cMax;
+ else
+ nSat = 0;
+
+ if( nSat == 0 )
+ nHue = 0; // Default = undefined
+ else
+ {
+ double dHue = 0.0;
+
+ if( c[0] == cMax )
+ {
+ dHue = static_cast<double>( c[1] - c[2] ) / static_cast<double>(cDelta);
+ }
+ else if( c[1] == cMax )
+ {
+ dHue = 2.0 + static_cast<double>( c[2] - c[0] ) / static_cast<double>(cDelta);
+ }
+ else if ( c[2] == cMax )
+ {
+ dHue = 4.0 + static_cast<double>( c[0] - c[1] ) / static_cast<double>(cDelta);
+ }
+ dHue *= 60.0;
+
+ if( dHue < 0.0 )
+ dHue += 360.0;
+
+ nHue = static_cast<sal_uInt16>(dHue);
+ }
+}
+
+Color Color::HSBtoRGB( sal_uInt16 nHue, sal_uInt16 nSat, sal_uInt16 nBri )
+{
+ sal_uInt8 cR=0,cG=0,cB=0;
+ sal_uInt8 nB = static_cast<sal_uInt8>( nBri * 255 / 100 );
+
+ if( nSat == 0 )
+ {
+ cR = nB;
+ cG = nB;
+ cB = nB;
+ }
+ else
+ {
+ double dH = nHue;
+ double f;
+ sal_uInt16 n;
+ if( dH == 360.0 )
+ dH = 0.0;
+
+ dH /= 60.0;
+ n = static_cast<sal_uInt16>(dH);
+ f = dH - n;
+
+ sal_uInt8 a = static_cast<sal_uInt8>( nB * ( 100 - nSat ) / 100 );
+ sal_uInt8 b = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * f ) ) / 100 );
+ sal_uInt8 c = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * ( 1.0 - f ) ) ) / 100 );
+
+ switch( n )
+ {
+ case 0: cR = nB; cG = c; cB = a; break;
+ case 1: cR = b; cG = nB; cB = a; break;
+ case 2: cR = a; cG = nB; cB = c; break;
+ case 3: cR = a; cG = b; cB = nB; break;
+ case 4: cR = c; cG = a; cB = nB; break;
+ case 5: cR = nB; cG = a; cB = b; break;
+ }
+ }
+
+ return Color( cR, cG, cB );
+}
+
+Color Color::STRtoRGB(std::u16string_view colorname)
+{
+ Color col;
+ if(colorname.empty()) return col;
+
+ switch(colorname.size()){
+ case 7:
+ col.mValue = o3tl::toUInt32(colorname.substr(1,6), 16);
+ break;
+ case 6:
+ col.mValue = o3tl::toUInt32(colorname, 16);
+ break;
+ case 4:
+ {
+ sal_Unicode data[6] = { colorname[1], colorname[1], colorname[2],
+ colorname[2], colorname[3], colorname[3] };
+ col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16);
+ break;
+ }
+ case 3:
+ {
+ sal_Unicode data[6] = { colorname[0], colorname[0], colorname[1],
+ colorname[1], colorname[2], colorname[2] };
+ col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16);
+ break;
+ }
+ default:
+ break;
+ }
+ return col;
+}
+
+OUString Color::AsRGBHexString() const
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor());
+ return OUString::createFromAscii(ss.str());
+}
+
+OUString Color::AsRGBHEXString() const
+{
+ std::stringstream ss;
+ ss << std::hex << std::uppercase << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor());
+ return OUString::createFromAscii(ss.str());
+}
+
+void Color::ApplyTintOrShade(sal_Int16 n100thPercent)
+{
+ if (n100thPercent == 0)
+ return;
+
+ basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor());
+ double fFactor = 1.0 - (std::abs(double(n100thPercent)) / 10000.0);
+ double fResult;
+
+ if (n100thPercent > 0) // tint
+ {
+ fResult = aBColor.getBlue() * fFactor + (1.0 - fFactor);
+ }
+ else // shade
+ {
+ fResult = aBColor.getBlue() * fFactor;
+ }
+
+ aBColor.setBlue(fResult);
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+
+ R = sal_uInt8(std::lround(aBColor.getRed() * 255.0));
+ G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0));
+ B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0));
+}
+
+void Color::ApplyLumModOff(sal_Int16 nMod, sal_Int16 nOff)
+{
+ if (nMod == 10000 && nOff == 0)
+ {
+ return;
+ }
+ // Switch to HSL, where applying these transforms is easier.
+ basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor());
+
+ // 50% is half luminance, 200% is double luminance. Unit is 100th percent.
+ aBColor.setBlue(std::clamp(aBColor.getBlue() * nMod / 10000, 0.0, 1.0));
+ // If color changes to black or white, it will stay gray if luminance changes again.
+ if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0))
+ {
+ aBColor.setGreen(0.0);
+ }
+
+ // Luminance offset means hue and saturation is left unchanged. Unit is 100th percent.
+ aBColor.setBlue(std::clamp(aBColor.getBlue() + static_cast<double>(nOff) / 10000, 0.0, 1.0));
+ // If color changes to black or white, it will stay gray if luminance changes again.
+ if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0))
+ {
+ aBColor.setGreen(0.0);
+ }
+
+ // Switch back to RGB.
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+ R = sal_uInt8(std::lround(aBColor.getRed() * 255.0));
+ G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0));
+ B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/config.cxx b/tools/source/generic/config.cxx
new file mode 100644
index 0000000000..cc5cce9c3b
--- /dev/null
+++ b/tools/source/generic/config.cxx
@@ -0,0 +1,944 @@
+/* -*- 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 <cstddef>
+#include <cstdlib>
+#include <string.h>
+
+#ifdef _WIN32
+#include <stdlib.h>
+#endif
+
+#include <osl/file.hxx>
+#include <tools/config.hxx>
+#include <sal/log.hxx>
+
+namespace {
+
+struct ImplKeyData
+{
+ ImplKeyData* mpNext;
+ OString maKey;
+ OString maValue;
+ bool mbIsComment;
+};
+
+}
+
+struct ImplGroupData
+{
+ ImplGroupData* mpNext;
+ ImplKeyData* mpFirstKey;
+ OString maGroupName;
+ sal_uInt16 mnEmptyLines;
+};
+
+struct ImplConfigData
+{
+ ImplGroupData* mpFirstGroup;
+ OUString maFileName;
+ sal_uInt32 mnDataUpdateId;
+ sal_uInt32 mnTimeStamp;
+ bool mbModified;
+ bool mbRead;
+ bool mbIsUTF8BOM;
+};
+
+static OUString toUncPath( const OUString& rPath )
+{
+ OUString aFileURL;
+
+ // check if rFileName is already a URL; if not make it so
+ if( rPath.startsWith( "file://"))
+ {
+ aFileURL = rPath;
+ }
+ else if( ::osl::FileBase::getFileURLFromSystemPath( rPath, aFileURL ) != ::osl::FileBase::E_None )
+ {
+ aFileURL = rPath;
+ }
+ return aFileURL;
+}
+
+static sal_uInt32 ImplSysGetConfigTimeStamp( const OUString& rFileName )
+{
+ sal_uInt32 nTimeStamp = 0;
+ ::osl::DirectoryItem aItem;
+ ::osl::FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+
+ if( ::osl::DirectoryItem::get( rFileName, aItem ) == ::osl::FileBase::E_None &&
+ aItem.getFileStatus( aStatus ) == ::osl::FileBase::E_None )
+ {
+ nTimeStamp = aStatus.getModifyTime().Seconds;
+ }
+
+ return nTimeStamp;
+}
+
+static std::unique_ptr<sal_uInt8[]> ImplSysReadConfig( const OUString& rFileName,
+ sal_uInt64& rRead, bool& rbRead, bool& rbIsUTF8BOM, sal_uInt32& rTimeStamp )
+{
+ std::unique_ptr<sal_uInt8[]> pBuf;
+ ::osl::File aFile( rFileName );
+
+ if( aFile.open( osl_File_OpenFlag_Read ) == ::osl::FileBase::E_None )
+ {
+ sal_uInt64 nPos = 0;
+ if( aFile.getSize( nPos ) == ::osl::FileBase::E_None )
+ {
+ if (nPos > SAL_MAX_SIZE) {
+ aFile.close();
+ return nullptr;
+ }
+ pBuf.reset(new sal_uInt8[static_cast< std::size_t >(nPos)]);
+ sal_uInt64 nRead = 0;
+ if( aFile.read( pBuf.get(), nPos, nRead ) == ::osl::FileBase::E_None && nRead == nPos )
+ {
+ //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files
+ unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF};
+ if (nRead > 2 && memcmp(pBuf.get(), BOM, 3) == 0)
+ {
+ nRead -= 3;
+ memmove(pBuf.get(), pBuf.get() + 3, sal::static_int_cast<std::size_t>(nRead * sizeof(sal_uInt8)) );
+ rbIsUTF8BOM = true;
+ }
+
+ rTimeStamp = ImplSysGetConfigTimeStamp( rFileName );
+ rbRead = true;
+ rRead = nRead;
+ }
+ else
+ {
+ pBuf.reset();
+ }
+ }
+ aFile.close();
+ }
+
+ return pBuf;
+}
+
+static bool ImplSysWriteConfig( const OUString& rFileName,
+ const sal_uInt8* pBuf, sal_uInt32 nBufLen, bool rbIsUTF8BOM, sal_uInt32& rTimeStamp )
+{
+ bool bSuccess = false;
+ bool bUTF8BOMSuccess = false;
+
+ ::osl::File aFile( rFileName );
+ ::osl::FileBase::RC eError = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+ if( eError != ::osl::FileBase::E_None )
+ eError = aFile.open( osl_File_OpenFlag_Write );
+ if( eError == ::osl::FileBase::E_None )
+ {
+ // truncate
+ aFile.setSize( 0 );
+ sal_uInt64 nWritten;
+
+ //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files
+ if ( rbIsUTF8BOM )
+ {
+ unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF};
+ sal_uInt64 nUTF8BOMWritten;
+ if( aFile.write( BOM, 3, nUTF8BOMWritten ) == ::osl::FileBase::E_None && 3 == nUTF8BOMWritten )
+ {
+ bUTF8BOMSuccess = true;
+ }
+ }
+
+ if( aFile.write( pBuf, nBufLen, nWritten ) == ::osl::FileBase::E_None && nWritten == nBufLen )
+ {
+ bSuccess = true;
+ }
+ if ( rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess )
+ {
+ rTimeStamp = ImplSysGetConfigTimeStamp( rFileName );
+ }
+ }
+
+ return rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess;
+}
+
+namespace {
+OString makeOString(const sal_uInt8* p, sal_uInt64 n)
+{
+ if (n > SAL_MAX_INT32)
+ {
+ #ifdef _WIN32
+ abort();
+ #else
+ ::std::abort(); //TODO: handle this gracefully
+ #endif
+ }
+ return OString(
+ reinterpret_cast< char const * >(p),
+ sal::static_int_cast< sal_Int32 >(n));
+}
+}
+
+static void ImplMakeConfigList( ImplConfigData* pData,
+ const sal_uInt8* pBuf, sal_uInt64 nLen )
+{
+ if ( !nLen )
+ return;
+
+ // Parse buffer and build config list
+ sal_uInt64 nStart;
+ sal_uInt64 nLineLen;
+ sal_uInt64 nNameLen;
+ sal_uInt64 nKeyLen;
+ sal_uInt64 i;
+ const sal_uInt8* pLine;
+ ImplKeyData* pPrevKey = nullptr;
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = nullptr;
+ i = 0;
+ while ( i < nLen )
+ {
+ // Ctrl+Z
+ if ( pBuf[i] == 0x1A )
+ break;
+
+ // Remove spaces and tabs
+ while ( (pBuf[i] == ' ') || (pBuf[i] == '\t') )
+ i++;
+
+ // remember line-starts
+ nStart = i;
+ pLine = pBuf+i;
+
+ // search line-endings
+ while ( (i < nLen) && pBuf[i] && (pBuf[i] != '\r') && (pBuf[i] != '\n') &&
+ (pBuf[i] != 0x1A) )
+ i++;
+
+ nLineLen = i-nStart;
+
+ // if Line-ending is found, continue once
+ if ( (i+1 < nLen) &&
+ (pBuf[i] != pBuf[i+1]) &&
+ ((pBuf[i+1] == '\r') || (pBuf[i+1] == '\n')) )
+ i++;
+ i++;
+
+ // evaluate line
+ if ( *pLine == '[' )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 0;
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup;
+ else
+ pData->mpFirstGroup = pGroup;
+ pPrevGroup = pGroup;
+ pPrevKey = nullptr;
+
+ // filter group names
+ pLine++;
+ nLineLen--;
+ // remove spaces and tabs
+ while ( (*pLine == ' ') || (*pLine == '\t') )
+ {
+ nLineLen--;
+ pLine++;
+ }
+ nNameLen = 0;
+ while ( (nNameLen < nLineLen) && (pLine[nNameLen] != ']') )
+ nNameLen++;
+ if ( nNameLen )
+ {
+ while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') )
+ nNameLen--;
+ }
+ pGroup->maGroupName = makeOString(pLine, nNameLen);
+ }
+ else
+ {
+ if ( nLineLen )
+ {
+ // If no group exists yet, add to default
+ if ( !pGroup )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 0;
+ pData->mpFirstGroup = pGroup;
+ pPrevGroup = pGroup;
+ pPrevKey = nullptr;
+ }
+
+ // if empty line, append it
+ if ( pPrevKey )
+ {
+ while ( pGroup->mnEmptyLines )
+ {
+ ImplKeyData* pKey = new ImplKeyData;
+ pKey->mbIsComment = true;
+ pPrevKey->mpNext = pKey;
+ pPrevKey = pKey;
+ pGroup->mnEmptyLines--;
+ }
+ }
+
+ // Generate new key
+ ImplKeyData* pKey = new ImplKeyData;
+ pKey->mpNext = nullptr;
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey;
+ else
+ pGroup->mpFirstKey = pKey;
+ pPrevKey = pKey;
+ if ( pLine[0] == ';' )
+ {
+ pKey->maValue = makeOString(pLine, nLineLen);
+ pKey->mbIsComment = true;
+ }
+ else
+ {
+ pKey->mbIsComment = false;
+ nNameLen = 0;
+ while ( (nNameLen < nLineLen) && (pLine[nNameLen] != '=') )
+ nNameLen++;
+ nKeyLen = nNameLen;
+ // Remove spaces and tabs
+ if ( nNameLen )
+ {
+ while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') )
+ nNameLen--;
+ }
+ pKey->maKey = makeOString(pLine, nNameLen);
+ nKeyLen++;
+ if ( nKeyLen < nLineLen )
+ {
+ pLine += nKeyLen;
+ nLineLen -= nKeyLen;
+ // Remove spaces and tabs
+ while ( (*pLine == ' ') || (*pLine == '\t') )
+ {
+ nLineLen--;
+ pLine++;
+ }
+ if ( nLineLen )
+ {
+ while ( (pLine[nLineLen-1] == ' ') || (pLine[nLineLen-1] == '\t') )
+ nLineLen--;
+ pKey->maValue = makeOString(pLine, nLineLen);
+ }
+ }
+ }
+ }
+ else
+ {
+ // Spaces are counted and appended only after key generation,
+ // as we want to store spaces even after adding new keys
+ if ( pGroup )
+ pGroup->mnEmptyLines++;
+ }
+ }
+ }
+}
+
+static std::unique_ptr<sal_uInt8[]> ImplGetConfigBuffer( const ImplConfigData* pData, sal_uInt32& rLen )
+{
+ std::unique_ptr<sal_uInt8[]> pWriteBuf;
+ sal_uInt8* pBuf;
+ sal_uInt8 aLineEndBuf[2] = {0, 0};
+ ImplKeyData* pKey;
+ ImplGroupData* pGroup;
+ sal_uInt32 nBufLen;
+ sal_uInt32 nValueLen;
+ sal_uInt32 nKeyLen;
+ sal_uInt32 nLineEndLen;
+
+ aLineEndBuf[0] = '\r';
+ aLineEndBuf[1] = '\n';
+ nLineEndLen = 2;
+
+ nBufLen = 0;
+ pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ // Don't write empty groups
+ if ( pGroup->mpFirstKey )
+ {
+ nBufLen += pGroup->maGroupName.getLength() + nLineEndLen + 2;
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ nValueLen = pKey->maValue.getLength();
+ if ( pKey->mbIsComment )
+ nBufLen += nValueLen + nLineEndLen;
+ else
+ nBufLen += pKey->maKey.getLength() + nValueLen + nLineEndLen + 1;
+
+ pKey = pKey->mpNext;
+ }
+
+ // Write empty lines after each group
+ if ( !pGroup->mnEmptyLines )
+ pGroup->mnEmptyLines = 1;
+ nBufLen += nLineEndLen * pGroup->mnEmptyLines;
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ // Output buffer length
+ rLen = nBufLen;
+ if ( !nBufLen )
+ {
+ pWriteBuf.reset(new sal_uInt8[nLineEndLen]);
+ pWriteBuf[0] = aLineEndBuf[0];
+ if ( nLineEndLen == 2 )
+ pWriteBuf[1] = aLineEndBuf[1];
+ return pWriteBuf;
+ }
+
+ // Allocate new write buffer (caller frees it)
+ pWriteBuf.reset(new sal_uInt8[nBufLen]);
+
+ // fill buffer
+ pBuf = pWriteBuf.get();
+ pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ // Don't write empty groups
+ if ( pGroup->mpFirstKey )
+ {
+ *pBuf = '['; pBuf++;
+ memcpy( pBuf, pGroup->maGroupName.getStr(), pGroup->maGroupName.getLength() );
+ pBuf += pGroup->maGroupName.getLength();
+ *pBuf = ']'; pBuf++;
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ nValueLen = pKey->maValue.getLength();
+ if ( pKey->mbIsComment )
+ {
+ if ( nValueLen )
+ {
+ memcpy( pBuf, pKey->maValue.getStr(), nValueLen );
+ pBuf += nValueLen;
+ }
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ }
+ else
+ {
+ nKeyLen = pKey->maKey.getLength();
+ memcpy( pBuf, pKey->maKey.getStr(), nKeyLen );
+ pBuf += nKeyLen;
+ *pBuf = '='; pBuf++;
+ memcpy( pBuf, pKey->maValue.getStr(), nValueLen );
+ pBuf += nValueLen;
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ }
+
+ pKey = pKey->mpNext;
+ }
+
+ // Store empty line after each group
+ sal_uInt16 nEmptyLines = pGroup->mnEmptyLines;
+ while ( nEmptyLines )
+ {
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ nEmptyLines--;
+ }
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ return pWriteBuf;
+}
+
+static void ImplReadConfig( ImplConfigData* pData )
+{
+ sal_uInt32 nTimeStamp = 0;
+ sal_uInt64 nRead = 0;
+ bool bRead = false;
+ bool bIsUTF8BOM = false;
+ std::unique_ptr<sal_uInt8[]> pBuf = ImplSysReadConfig( pData->maFileName, nRead, bRead, bIsUTF8BOM, nTimeStamp );
+
+ // Read config list from buffer
+ if ( pBuf )
+ {
+ ImplMakeConfigList( pData, pBuf.get(), nRead );
+ pBuf.reset();
+ }
+ pData->mnTimeStamp = nTimeStamp;
+ pData->mbModified = false;
+ if ( bRead )
+ pData->mbRead = true;
+ if ( bIsUTF8BOM )
+ pData->mbIsUTF8BOM = true;
+}
+
+static void ImplWriteConfig( ImplConfigData* pData )
+{
+ SAL_WARN_IF( pData->mnTimeStamp != ImplSysGetConfigTimeStamp( pData->maFileName ),
+ "tools.generic", "Config overwrites modified configfile: " << pData->maFileName );
+
+ // Read config list from buffer
+ sal_uInt32 nBufLen;
+ std::unique_ptr<sal_uInt8[]> pBuf = ImplGetConfigBuffer( pData, nBufLen );
+ if ( pBuf )
+ {
+ if ( ImplSysWriteConfig( pData->maFileName, pBuf.get(), nBufLen, pData->mbIsUTF8BOM, pData->mnTimeStamp ) )
+ pData->mbModified = false;
+ }
+ else
+ pData->mbModified = false;
+}
+
+static void ImplDeleteConfigData( ImplConfigData* pData )
+{
+ ImplKeyData* pTempKey;
+ ImplKeyData* pKey;
+ ImplGroupData* pTempGroup;
+ ImplGroupData* pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ pTempGroup = pGroup->mpNext;
+
+ // remove all keys
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ pTempKey = pKey->mpNext;
+ delete pKey;
+ pKey = pTempKey;
+ }
+
+ // remove group and continue
+ delete pGroup;
+ pGroup = pTempGroup;
+ }
+
+ pData->mpFirstGroup = nullptr;
+}
+
+static std::unique_ptr<ImplConfigData> ImplGetConfigData( const OUString& rFileName )
+{
+ std::unique_ptr<ImplConfigData> pData(new ImplConfigData);
+ pData->maFileName = rFileName;
+ pData->mpFirstGroup = nullptr;
+ pData->mnDataUpdateId = 0;
+ pData->mbRead = false;
+ pData->mbIsUTF8BOM = false;
+ ImplReadConfig( pData.get() );
+
+ return pData;
+}
+
+bool Config::ImplUpdateConfig() const
+{
+ // Re-read file if timestamp differs
+ if ( mpData->mnTimeStamp != ImplSysGetConfigTimeStamp( maFileName ) )
+ {
+ ImplDeleteConfigData( mpData.get() );
+ ImplReadConfig( mpData.get() );
+ mpData->mnDataUpdateId++;
+ return true;
+ }
+ else
+ return false;
+}
+
+ImplGroupData* Config::ImplGetGroup() const
+{
+ if ( !mpActGroup || (mnDataUpdateId != mpData->mnDataUpdateId) )
+ {
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ while ( pGroup )
+ {
+ if ( pGroup->maGroupName.equalsIgnoreAsciiCase(maGroupName) )
+ break;
+
+ pPrevGroup = pGroup;
+ pGroup = pGroup->mpNext;
+ }
+
+ // Add group if not exists
+ if ( !pGroup )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 1;
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup;
+ else
+ mpData->mpFirstGroup = pGroup;
+ }
+
+ // Always inherit group names and update cache members
+ pGroup->maGroupName = maGroupName;
+ const_cast<Config*>(this)->mnDataUpdateId = mpData->mnDataUpdateId;
+ const_cast<Config*>(this)->mpActGroup = pGroup;
+ }
+
+ return mpActGroup;
+}
+
+Config::Config( const OUString& rFileName )
+{
+ // Initialize config data
+ maFileName = toUncPath( rFileName );
+ mpData = ImplGetConfigData( maFileName );
+ mpActGroup = nullptr;
+ mnDataUpdateId = 0;
+
+ SAL_INFO("tools.generic", "Config::Config( " << maFileName << " )");
+}
+
+Config::~Config()
+{
+ SAL_INFO("tools.generic", "Config::~Config()" );
+
+ Flush();
+ ImplDeleteConfigData( mpData.get() );
+}
+
+void Config::SetGroup(const OString& rGroup)
+{
+ // If group is to be reset, it needs to be updated on next call
+ if ( maGroupName != rGroup )
+ {
+ maGroupName = rGroup;
+ mnDataUpdateId = mpData->mnDataUpdateId-1;
+ }
+}
+
+void Config::DeleteGroup(std::string_view rGroup)
+{
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ while ( pGroup )
+ {
+ if ( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) )
+ break;
+
+ pPrevGroup = pGroup;
+ pGroup = pGroup->mpNext;
+ }
+
+ if ( !pGroup )
+ return;
+
+ // Remove all keys
+ ImplKeyData* pTempKey;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ pTempKey = pKey->mpNext;
+ delete pKey;
+ pKey = pTempKey;
+ }
+
+ // Rewire pointers and remove group
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup->mpNext;
+ else
+ mpData->mpFirstGroup = pGroup->mpNext;
+ delete pGroup;
+
+ // Rewrite config data
+ mpData->mbModified = true;
+
+ mnDataUpdateId = mpData->mnDataUpdateId;
+ mpData->mnDataUpdateId++;
+}
+
+OString Config::GetGroupName(sal_uInt16 nGroup) const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ sal_uInt16 nGroupCount = 0;
+ OString aGroupName;
+ while ( pGroup )
+ {
+ if ( nGroup == nGroupCount )
+ {
+ aGroupName = pGroup->maGroupName;
+ break;
+ }
+
+ nGroupCount++;
+ pGroup = pGroup->mpNext;
+ }
+
+ return aGroupName;
+}
+
+sal_uInt16 Config::GetGroupCount() const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ sal_uInt16 nGroupCount = 0;
+ while ( pGroup )
+ {
+ nGroupCount++;
+ pGroup = pGroup->mpNext;
+ }
+
+ return nGroupCount;
+}
+
+bool Config::HasGroup(std::string_view rGroup) const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ bool bRet = false;
+
+ while( pGroup )
+ {
+ if( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) )
+ {
+ bRet = true;
+ break;
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ return bRet;
+}
+
+OString Config::ReadKey(const OString& rKey) const
+{
+ return ReadKey(rKey, OString());
+}
+
+OString Config::ReadKey(const OString& rKey, const OString& rDefault) const
+{
+ SAL_INFO("tools.generic", "Config::ReadKey( " << rKey << " ) from " << GetGroup()
+ << " in " << maFileName);
+
+ // Search key, return value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ return pKey->maValue;
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return rDefault;
+}
+
+void Config::WriteKey(const OString& rKey, const OString& rStr)
+{
+ SAL_INFO("tools.generic", "Config::WriteKey( " << rKey << ", " << rStr << " ) to "
+ << GetGroup() << " in " << maFileName);
+
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ // Search key and update value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( !pGroup )
+ return;
+
+ ImplKeyData* pPrevKey = nullptr;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ break;
+
+ pPrevKey = pKey;
+ pKey = pKey->mpNext;
+ }
+
+ bool bNewValue;
+ if ( !pKey )
+ {
+ pKey = new ImplKeyData;
+ pKey->mpNext = nullptr;
+ pKey->maKey = rKey;
+ pKey->mbIsComment = false;
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey;
+ else
+ pGroup->mpFirstKey = pKey;
+ bNewValue = true;
+ }
+ else
+ bNewValue = pKey->maValue != rStr;
+
+ if ( bNewValue )
+ {
+ pKey->maValue = rStr;
+
+ mpData->mbModified = true;
+ }
+}
+
+void Config::DeleteKey(std::string_view rKey)
+{
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ // Search key and update value
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( !pGroup )
+ return;
+
+ ImplKeyData* pPrevKey = nullptr;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ break;
+
+ pPrevKey = pKey;
+ pKey = pKey->mpNext;
+ }
+
+ if ( pKey )
+ {
+ // Rewire group pointers and delete
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey->mpNext;
+ else
+ pGroup->mpFirstKey = pKey->mpNext;
+ delete pKey;
+
+ mpData->mbModified = true;
+ }
+}
+
+sal_uInt16 Config::GetKeyCount() const
+{
+ SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName);
+
+ // Search key and update value
+ sal_uInt16 nCount = 0;
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ nCount++;
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return nCount;
+}
+
+OString Config::GetKeyName(sal_uInt16 nKey) const
+{
+ SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32>(nKey))
+ << " ) from " << GetGroup() << " in " << maFileName);
+
+ // search key and return name if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ {
+ if ( !nKey )
+ return pKey->maKey;
+ nKey--;
+ }
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return OString();
+}
+
+OString Config::ReadKey(sal_uInt16 nKey) const
+{
+ SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32>(nKey))
+ << " ) from " << GetGroup() << " in " << maFileName);
+
+ // Search key and return value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ {
+ if ( !nKey )
+ return pKey->maValue;
+ nKey--;
+ }
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return OString();
+}
+
+void Config::Flush()
+{
+ if ( mpData->mbModified )
+ ImplWriteConfig( mpData.get() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/fract.cxx b/tools/source/generic/fract.cxx
new file mode 100644
index 0000000000..abe8ee41b2
--- /dev/null
+++ b/tools/source/generic/fract.cxx
@@ -0,0 +1,537 @@
+/* -*- 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 <tools/fract.hxx>
+#include <tools/debug.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <cmath>
+#include <numeric>
+
+#include <boost/rational.hpp>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+static boost::rational<sal_Int32> rational_FromDouble(double dVal);
+static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits);
+static int impl_NumberOfBits( sal_uInt32 nNum );
+
+static boost::rational<sal_Int32> toRational(sal_Int32 n, sal_Int32 d)
+{
+ // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min
+ if (n == d)
+ return 1;
+ // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648
+ if (d < -std::numeric_limits<sal_Int32>::max())
+ return 0;
+ return boost::rational<sal_Int32>(n, d);
+}
+
+static constexpr bool isOutOfRange(sal_Int64 nNum)
+{
+ return nNum < std::numeric_limits<sal_Int32>::min()
+ || nNum > std::numeric_limits<sal_Int32>::max();
+}
+
+Fraction::Fraction( sal_Int64 nNum, sal_Int64 nDen ) : mnNumerator(nNum), mnDenominator(nDen)
+{
+ if ( isOutOfRange(nNum) || isOutOfRange(nDen) )
+ {
+ // tdf#143200
+ if (const auto gcd = std::gcd(nNum, nDen); gcd > 1)
+ {
+ nNum /= gcd;
+ nDen /= gcd;
+ }
+ SAL_WARN_IF(isOutOfRange(nNum) || isOutOfRange(nDen),
+ "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision");
+ while (isOutOfRange(nNum) || isOutOfRange(nDen))
+ {
+ nNum /= 2;
+ nDen /= 2;
+ }
+ mnNumerator = nNum;
+ mnDenominator = nDen;
+ }
+ if ( mnDenominator == 0 )
+ {
+ mbValid = false;
+ SAL_WARN( "tools.fraction", "'Fraction(" << nNum << ",0)' invalid fraction created" );
+ return;
+ }
+ else if ((nDen == -1 && nNum == std::numeric_limits<sal_Int32>::min()) ||
+ (nNum == -1 && nDen == std::numeric_limits<sal_Int32>::min()))
+ {
+ mbValid = false;
+ SAL_WARN("tools.fraction", "'Fraction(" << nNum << "," << nDen << ")' invalid fraction created");
+ return;
+ }
+}
+
+/**
+ * only here to prevent passing of NaN
+ */
+Fraction::Fraction( double nNum, double nDen )
+ : Fraction(sal_Int64(nNum), sal_Int64(nDen))
+{}
+
+Fraction::Fraction( double dVal )
+{
+ try
+ {
+ boost::rational<sal_Int32> v = rational_FromDouble( dVal );
+ mnNumerator = v.numerator();
+ mnDenominator = v.denominator();
+ }
+ catch (const boost::bad_rational&)
+ {
+ mbValid = false;
+ SAL_WARN( "tools.fraction", "'Fraction(" << dVal << ")' invalid fraction created" );
+ }
+}
+
+Fraction::operator double() const
+{
+ if (!mbValid)
+ {
+ SAL_WARN( "tools.fraction", "'double()' on invalid fraction" );
+ return 0.0;
+ }
+
+ return boost::rational_cast<double>(toRational(mnNumerator, mnDenominator));
+}
+
+// This methods first validates both values.
+// If one of the arguments is invalid, the whole operation is invalid.
+// After computation detect if result overflows a sal_Int32 value
+// which cause the operation to be marked as invalid
+Fraction& Fraction::operator += ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator +=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a += toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+Fraction& Fraction::operator -= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a -= toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+namespace
+{
+ bool checked_multiply_by(boost::rational<sal_Int32>& i, const boost::rational<sal_Int32>& r)
+ {
+ // Protect against self-modification
+ sal_Int32 num = r.numerator();
+ sal_Int32 den = r.denominator();
+
+ // Fast-path if the number of bits in input is < the number of bits in the output, overflow cannot happen
+ // This is considerably faster than repeated std::gcd() operations
+ if ((impl_NumberOfBits(std::abs(i.numerator())) + impl_NumberOfBits(std::abs(r.numerator()))) < 32 &&
+ (impl_NumberOfBits(std::abs(i.denominator())) + impl_NumberOfBits(std::abs(r.denominator()))) < 32)
+ {
+ i *= r;
+ return false;
+ }
+
+ // Avoid overflow and preserve normalization
+ sal_Int32 gcd1 = std::gcd(i.numerator(), den);
+ sal_Int32 gcd2 = std::gcd(num, i.denominator());
+
+ if (!gcd1 || !gcd2)
+ return true;
+
+ bool fail = false;
+ fail |= o3tl::checked_multiply(i.numerator() / gcd1, num / gcd2, num);
+ fail |= o3tl::checked_multiply(i.denominator() / gcd2, den / gcd1, den);
+
+ if (!fail)
+ i.assign(num, den);
+
+ return fail;
+ }
+}
+
+Fraction& Fraction::operator *= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ boost::rational<sal_Int32> b = toRational(rVal.mnNumerator, rVal.mnDenominator);
+ bool bFail = checked_multiply_by(a, b);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ if (bFail)
+ {
+ mbValid = false;
+ }
+
+ return *this;
+}
+
+Fraction& Fraction::operator /= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a /= toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+/** Inaccurate cancellation for a fraction.
+
+ Clip both nominator and denominator to said number of bits. If
+ either of those already have equal or less number of bits used,
+ this method does nothing.
+
+ @param nSignificantBits denotes, how many significant binary
+ digits to maintain, in both nominator and denominator.
+
+ @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
+ largest error occurs with the following pair of values:
+
+ binary 1000000011111111111111111111111b/1000000000000000000000000000000b
+ = 1082130431/1073741824
+ = approx. 1.007812499
+
+ A ReduceInaccurate(8) yields 1/1.
+*/
+void Fraction::ReduceInaccurate( unsigned nSignificantBits )
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" );
+ return;
+ }
+
+ if ( !mnNumerator )
+ return;
+
+ auto a = toRational(mnNumerator, mnDenominator);
+ rational_ReduceInaccurate(a, nSignificantBits);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+}
+
+sal_Int32 Fraction::GetNumerator() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" );
+ return 0;
+ }
+ return mnNumerator;
+}
+
+sal_Int32 Fraction::GetDenominator() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" );
+ return -1;
+ }
+ return mnDenominator;
+}
+
+Fraction::operator sal_Int32() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" );
+ return 0;
+ }
+ return boost::rational_cast<sal_Int32>(toRational(mnNumerator, mnDenominator));
+}
+
+Fraction operator+( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg += rVal2;
+ return aErg;
+}
+
+Fraction operator-( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg -= rVal2;
+ return aErg;
+}
+
+Fraction operator*( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg *= rVal2;
+ return aErg;
+}
+
+Fraction operator/( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg /= rVal2;
+ return aErg;
+}
+
+bool operator !=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 == rVal2);
+}
+
+bool operator <=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 > rVal2);
+}
+
+bool operator >=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 < rVal2);
+}
+
+bool operator == ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) == toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+bool operator < ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator <' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) < toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+bool operator > ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator >' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) > toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+// If dVal > LONG_MAX or dVal < LONG_MIN, the rational throws a boost::bad_rational.
+// Otherwise, dVal and denominator are multiplied by 10, until one of them
+// is larger than (LONG_MAX / 10).
+//
+// NOTE: here we use 'sal_Int32' due that only values in sal_Int32 range are valid.
+static boost::rational<sal_Int32> rational_FromDouble(double dVal)
+{
+ if ( dVal > std::numeric_limits<sal_Int32>::max() ||
+ dVal < std::numeric_limits<sal_Int32>::min() ||
+ std::isnan(dVal) )
+ throw boost::bad_rational();
+
+ const sal_Int32 nMAX = std::numeric_limits<sal_Int32>::max() / 10;
+ sal_Int32 nDen = 1;
+ while ( std::abs( dVal ) < nMAX && nDen < nMAX ) {
+ dVal *= 10;
+ nDen *= 10;
+ }
+ return boost::rational<sal_Int32>( sal_Int32(dVal), nDen );
+}
+
+/**
+ * Find the number of bits required to represent this number, using the CLZ intrinsic
+ */
+static int impl_NumberOfBits( sal_uInt32 nNum )
+{
+ if (nNum == 0)
+ return 0;
+#ifdef _MSC_VER
+ unsigned long r = 0;
+ _BitScanReverse(&r, nNum);
+ return r + 1;
+#else
+ return 32 - __builtin_clz(nNum);
+#endif
+}
+
+/** Inaccurate cancellation for a fraction.
+
+ Clip both nominator and denominator to said number of bits. If
+ either of those already have equal or less number of bits used,
+ this method does nothing.
+
+ @param nSignificantBits denotes, how many significant binary
+ digits to maintain, in both nominator and denominator.
+
+ @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
+ largest error occurs with the following pair of values:
+
+ binary 1000000011111111111111111111111b/1000000000000000000000000000000b
+ = 1082130431/1073741824
+ = approx. 1.007812499
+
+ A ReduceInaccurate(8) yields 1/1.
+*/
+static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits)
+{
+ if ( !rRational )
+ return;
+
+ // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation
+ sal_Int32 nMul = rRational.numerator();
+ if (nMul == std::numeric_limits<sal_Int32>::min())
+ {
+ // ofz#32973 Integer-overflow
+ return;
+ }
+ const bool bNeg = nMul < 0;
+ if (bNeg)
+ nMul = -nMul;
+ sal_Int32 nDiv = rRational.denominator();
+
+ DBG_ASSERT(nSignificantBits<65, "More than 64 bit of significance is overkill!");
+
+ // How much bits can we lose?
+ const int nMulBitsToLose = std::max( ( impl_NumberOfBits( nMul ) - int( nSignificantBits ) ), 0 );
+ const int nDivBitsToLose = std::max( ( impl_NumberOfBits( nDiv ) - int( nSignificantBits ) ), 0 );
+
+ const int nToLose = std::min( nMulBitsToLose, nDivBitsToLose );
+
+ // Remove the bits
+ nMul >>= nToLose;
+ nDiv >>= nToLose;
+
+ if ( !nMul || !nDiv ) {
+ // Return without reduction
+ OSL_FAIL( "Oops, we reduced too much..." );
+ return;
+ }
+
+ rRational.assign( bNeg ? -nMul : nMul, nDiv );
+}
+
+size_t Fraction::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mnNumerator );
+ o3tl::hash_combine( hash, mnDenominator );
+ o3tl::hash_combine( hash, mbValid );
+ return hash;
+}
+
+Fraction Fraction::MakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 )
+{
+ if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
+ {
+ SAL_WARN("tools.fraction", "Invalid parameter for ImplMakeFraction");
+ return Fraction( 1, 1 );
+ }
+
+ tools::Long i = 1;
+
+ if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
+ if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
+ if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
+ if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
+ // all positive; i sign
+
+ assert( nN1 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nN1 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nD1 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nD1 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nN2 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nN2 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nD2 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nD2 <= std::numeric_limits<sal_Int32>::max( ));
+
+ boost::rational<sal_Int32> a = toRational(i*nN1, nD1);
+ boost::rational<sal_Int32> b = toRational(nN2, nD2);
+ bool bFail = checked_multiply_by(a, b);
+
+ while ( bFail ) {
+ if ( nN1 > nN2 )
+ nN1 = (nN1 + 1) / 2;
+ else
+ nN2 = (nN2 + 1) / 2;
+ if ( nD1 > nD2 )
+ nD1 = (nD1 + 1) / 2;
+ else
+ nD2 = (nD2 + 1) / 2;
+
+ a = toRational(i*nN1, nD1);
+ b = toRational(nN2, nD2);
+ bFail = checked_multiply_by(a, b);
+ }
+
+ return Fraction(a.numerator(), a.denominator());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/gen.cxx b/tools/source/generic/gen.cxx
new file mode 100644
index 0000000000..012318c021
--- /dev/null
+++ b/tools/source/generic/gen.cxx
@@ -0,0 +1,225 @@
+/* -*- 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 <sal/config.h>
+#include <rtl/string.hxx>
+
+#include <algorithm>
+#include <tuple>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/gen.hxx>
+
+OString Pair::toString() const
+{
+ // Note that this is not just used for debugging output but the
+ // format is parsed by external code (passed in callbacks to
+ // LibreOfficeKit clients). So don't change.
+ return OString::number(A()) + ", " + OString::number(B());
+}
+
+size_t Pair::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mnA );
+ o3tl::hash_combine( hash, mnB );
+ return hash;
+}
+
+void RectangleTemplateBase::SaturatingSetSize(const SizeTemplateBase& rSize)
+{
+ if (rSize.Width() < 0)
+ mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() + 1));
+ else if ( rSize.Width() > 0 )
+ mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() - 1));
+ else
+ SetWidthEmpty();
+
+ if ( rSize.Height() < 0 )
+ mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() + 1));
+ else if ( rSize.Height() > 0 )
+ mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() - 1));
+ else
+ SetHeightEmpty();
+}
+
+void RectangleTemplateBase::SaturatingSetPosX(tools::Long x)
+{
+ if (!IsWidthEmpty())
+ mnRight = o3tl::saturating_add(mnRight, x - mnLeft);
+ mnLeft = x;
+}
+
+void RectangleTemplateBase::SaturatingSetPosY(tools::Long y)
+{
+ if (!IsHeightEmpty())
+ mnBottom = o3tl::saturating_add(mnBottom, y - mnTop);
+ mnTop = y;
+}
+
+void RectangleTemplateBase::Union( const RectangleTemplateBase& rRect )
+{
+ if ( rRect.IsEmpty() )
+ return;
+
+ if ( IsEmpty() )
+ *this = rRect;
+ else
+ {
+ std::tie(mnLeft, mnRight) = std::minmax({ mnLeft, rRect.mnLeft, mnRight, rRect.mnRight });
+ std::tie(mnTop, mnBottom) = std::minmax({ mnTop, rRect.mnTop, mnBottom, rRect.mnBottom });
+ }
+}
+
+void RectangleTemplateBase::Intersection( const RectangleTemplateBase& rRect )
+{
+ if ( IsEmpty() )
+ return;
+ if ( rRect.IsEmpty() )
+ {
+ *this = tools::Rectangle();
+ return;
+ }
+
+ // Normalize rectangle
+ RectangleTemplateBase aTmpRect( rRect );
+ Normalize();
+ aTmpRect.Normalize();
+
+ // Perform intersection
+ mnLeft = std::max( mnLeft, aTmpRect.mnLeft );
+ mnRight = std::min( mnRight, aTmpRect.mnRight );
+ mnTop = std::max( mnTop, aTmpRect.mnTop );
+ mnBottom= std::min( mnBottom, aTmpRect.mnBottom );
+
+ // Determine if intersection is empty
+ if ( mnRight < mnLeft || mnBottom < mnTop )
+ *this = tools::Rectangle();
+}
+
+void RectangleTemplateBase::Normalize()
+{
+ if ((mnRight < mnLeft) && (!IsWidthEmpty()))
+ {
+ std::swap(mnLeft, mnRight);
+ }
+
+ if ((mnBottom < mnTop) && (!IsHeightEmpty()))
+ {
+ std::swap(mnBottom, mnTop);
+ }
+}
+
+bool RectangleTemplateBase::Contains( const PointTemplateBase& rPoint ) const
+{
+ if ( IsEmpty() )
+ return false;
+
+ if ( mnLeft <= mnRight )
+ {
+ if ( (rPoint.X() < mnLeft) || (rPoint.X() > mnRight) )
+ return false;
+ }
+ else
+ {
+ if ( (rPoint.X() > mnLeft) || (rPoint.X() < mnRight) )
+ return false;
+ }
+ if ( mnTop <= mnBottom )
+ {
+ if ( (rPoint.Y() < mnTop) || (rPoint.Y() > mnBottom) )
+ return false;
+ }
+ else
+ {
+ if ( (rPoint.Y() > mnTop) || (rPoint.Y() < mnBottom) )
+ return false;
+ }
+ return true;
+}
+
+bool RectangleTemplateBase::Contains( const RectangleTemplateBase& rRect ) const
+{
+ return Contains( PointTemplateBase{ rRect.Left(), rRect.Top() } )
+ && Contains( PointTemplateBase{ rRect.Right(), rRect.Bottom() } );
+}
+
+bool RectangleTemplateBase::Overlaps( const RectangleTemplateBase& rRect ) const
+{
+ // If there's no intersection, they don't overlap
+ RectangleTemplateBase aTmp(*this);
+ aTmp.Intersection(rRect);
+ return !aTmp.IsEmpty();
+}
+
+OString RectangleTemplateBase::toString() const
+{
+ // Note that this is not just used for debugging output but the
+ // format is parsed by external code (passed in callbacks to
+ // LibreOfficeKit clients). So don't change.
+ return OString::number(Left()) + ", "
+ + OString::number(Top()) + ", "
+ + OString::number(getOpenWidth()) + ", "
+ + OString::number(getOpenHeight());
+}
+
+void RectangleTemplateBase::expand(tools::Long nExpandBy)
+{
+ AdjustLeft(-nExpandBy);
+ AdjustTop(-nExpandBy);
+ AdjustRight(nExpandBy);
+ AdjustBottom(nExpandBy);
+}
+
+void RectangleTemplateBase::shrink(tools::Long nShrinkBy)
+{
+ mnLeft += nShrinkBy;
+ mnTop += nShrinkBy;
+ if (!IsWidthEmpty())
+ mnRight -= nShrinkBy;
+ if (!IsHeightEmpty())
+ mnBottom -= nShrinkBy;
+}
+
+tools::Long RectangleTemplateBase::AdjustRight(tools::Long nHorzMoveDelta)
+{
+ if (IsWidthEmpty())
+ mnRight = mnLeft + nHorzMoveDelta - 1;
+ else
+ mnRight += nHorzMoveDelta;
+ return mnRight;
+}
+
+tools::Long RectangleTemplateBase::AdjustBottom( tools::Long nVertMoveDelta )
+{
+ if (IsHeightEmpty())
+ mnBottom = mnTop + nVertMoveDelta - 1;
+ else
+ mnBottom += nVertMoveDelta;
+ return mnBottom;
+}
+
+static_assert( std::is_trivially_copyable< Pair >::value );
+static_assert( std::is_trivially_copyable< Point >::value );
+static_assert( std::is_trivially_copyable< Size >::value );
+static_assert( std::is_trivially_copyable< Range >::value );
+static_assert( std::is_trivially_copyable< Selection >::value );
+static_assert( std::is_trivially_copyable< tools::Rectangle >::value );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/line.cxx b/tools/source/generic/line.cxx
new file mode 100644
index 0000000000..ee9ad97979
--- /dev/null
+++ b/tools/source/generic/line.cxx
@@ -0,0 +1,140 @@
+/* -*- 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 <tools/line.hxx>
+#include <tools/helpers.hxx>
+
+#include <cmath>
+
+namespace tools
+{
+
+double Line::GetLength() const
+{
+ return hypot( maStart.X() - maEnd.X(), maStart.Y() - maEnd.Y() );
+}
+
+bool Line::Intersection( const Line& rLine, Point& rIntersection ) const
+{
+ double fX, fY;
+ bool bRet;
+
+ if( Intersection( rLine, fX, fY ) )
+ {
+ rIntersection.setX( FRound( fX ) );
+ rIntersection.setY( FRound( fY ) );
+ bRet = true;
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+bool Line::Intersection( const tools::Line& rLine, double& rIntersectionX, double& rIntersectionY ) const
+{
+ const double fAx = static_cast<double>(maEnd.X()) - maStart.X();
+ const double fAy = static_cast<double>(maEnd.Y()) - maStart.Y();
+ const double fBx = static_cast<double>(rLine.maStart.X()) - rLine.maEnd.X();
+ const double fBy = static_cast<double>(rLine.maStart.Y()) - rLine.maEnd.Y();
+ const double fDen = fAy * fBx - fAx * fBy;
+ bool bOk = false;
+
+ if( fDen != 0. )
+ {
+ const double fCx = static_cast<double>(maStart.X()) - rLine.maStart.X();
+ const double fCy = static_cast<double>(maStart.Y()) - rLine.maStart.Y();
+ const double fA = fBy * fCx - fBx * fCy;
+ const bool bGreater = ( fDen > 0. );
+
+ bOk = true;
+
+ if ( bGreater )
+ {
+ if ( ( fA < 0. ) || ( fA > fDen ) )
+ bOk = false;
+ }
+ else if ( ( fA > 0. ) || ( fA < fDen ) )
+ bOk = false;
+
+ if ( bOk )
+ {
+ const double fB = fAx * fCy - fAy * fCx;
+
+ if ( bGreater )
+ {
+ if ( ( fB < 0. ) || ( fB > fDen ) )
+ bOk = false;
+ }
+ else if ( ( fB > 0. ) || ( fB < fDen ) )
+ bOk = false;
+
+ if( bOk )
+ {
+ const double fAlpha = fA / fDen;
+
+ rIntersectionX = ( maStart.X() + fAlpha * fAx );
+ rIntersectionY = ( maStart.Y() + fAlpha * fAy );
+ }
+ }
+ }
+
+ return bOk;
+}
+
+double Line::GetDistance( const double& rPtX, const double& rPtY ) const
+{
+ double fDist;
+
+ if( maStart != maEnd )
+ {
+ const double fDistX = static_cast<double>(maEnd.X()) - maStart.X();
+ const double fDistY = static_cast<double>(maEnd.Y()) - maStart.Y();
+ const double fACX = static_cast<double>(maStart.X()) - rPtX;
+ const double fACY = static_cast<double>(maStart.Y()) - rPtY;
+ const double fL2 = fDistX * fDistX + fDistY * fDistY;
+ const double fR = ( fACY * -fDistY - fACX * fDistX ) / fL2;
+ const double fS = ( fACY * fDistX - fACX * fDistY ) / fL2;
+
+ if( fR < 0.0 )
+ {
+ fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY );
+
+ if( fS < 0.0 )
+ fDist *= -1.0;
+ }
+ else if( fR <= 1.0 )
+ fDist = fS * sqrt( fL2 );
+ else
+ {
+ fDist = hypot( maEnd.X() - rPtX, maEnd.Y() - rPtY );
+
+ if( fS < 0.0 )
+ fDist *= -1.0;
+ }
+ }
+ else
+ fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY );
+
+ return fDist;
+}
+
+} //namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/point.cxx b/tools/source/generic/point.cxx
new file mode 100644
index 0000000000..e71f60411a
--- /dev/null
+++ b/tools/source/generic/point.cxx
@@ -0,0 +1,86 @@
+/* -*- 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 <tools/gen.hxx>
+
+void PointTemplateBase::RotateAround( PointTemplateBase& rPoint,
+ Degree10 nOrientation ) const
+{
+ tools::Long nX = rPoint.mnA;
+ tools::Long nY = rPoint.mnB;
+ RotateAround(nX, nY, nOrientation);
+ rPoint.mnA = nX;
+ rPoint.mnB = nY;
+}
+
+void PointTemplateBase::RotateAround( tools::Long& rX, tools::Long& rY,
+ Degree10 nOrientation ) const
+{
+ const tools::Long nOriginX = mnA;
+ const tools::Long nOriginY = mnB;
+
+ if ( (nOrientation >= 0_deg10) && !(nOrientation % 900_deg10) )
+ {
+ if ( nOrientation >= 3600_deg10 )
+ nOrientation %= 3600_deg10;
+
+ if ( nOrientation )
+ {
+ rX -= nOriginX;
+ rY -= nOriginY;
+
+ if ( nOrientation == 900_deg10 )
+ {
+ tools::Long nTemp = rX;
+ rX = rY;
+ rY = -nTemp;
+ }
+ else if ( nOrientation == 1800_deg10 )
+ {
+ rX = -rX;
+ rY = -rY;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ tools::Long nTemp = rX;
+ rX = -rY;
+ rY = nTemp;
+ }
+
+ rX += nOriginX;
+ rY += nOriginY;
+ }
+ }
+ else
+ {
+ double nRealOrientation = toRadians(nOrientation);
+ double nCos = cos( nRealOrientation );
+ double nSin = sin( nRealOrientation );
+
+ // Translation...
+ tools::Long nX = rX-nOriginX;
+ tools::Long nY = rY-nOriginY;
+
+ // Rotation...
+ rX = + static_cast<tools::Long>(nCos*nX + nSin*nY) + nOriginX;
+ rY = - static_cast<tools::Long>(nSin*nX - nCos*nY) + nOriginY;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/poly.cxx b/tools/source/generic/poly.cxx
new file mode 100644
index 0000000000..89405eea41
--- /dev/null
+++ b/tools/source/generic/poly.cxx
@@ -0,0 +1,1866 @@
+/* -*- 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 <osl/endian.h>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/bigint.hxx>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/gen.hxx>
+#include <poly.h>
+#include <o3tl/safeint.hxx>
+#include <tools/line.hxx>
+#include <tools/poly.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <memory>
+#include <vector>
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <limits.h>
+#include <cmath>
+
+constexpr int EDGE_LEFT = 1;
+constexpr int EDGE_TOP = 2;
+constexpr int EDGE_RIGHT = 4;
+constexpr int EDGE_BOTTOM = 8;
+constexpr int EDGE_HORZ = EDGE_RIGHT | EDGE_LEFT;
+constexpr int EDGE_VERT = EDGE_TOP | EDGE_BOTTOM;
+constexpr double SMALL_DVALUE = 0.0000001;
+#define FSQRT2 1.4142135623730950488016887242097
+
+static double ImplGetParameter( const Point& rCenter, const Point& rPt, double fWR, double fHR )
+{
+ const double nDX = static_cast<double>(rPt.X()) - rCenter.X();
+ const double nDY = static_cast<double>(rCenter.Y()) - rPt.Y();
+ double fAngle = atan2(nDY, nDX);
+
+ return atan2(fWR*sin(fAngle), fHR*cos(fAngle));
+}
+
+ImplPolygon::ImplPolygon( sal_uInt16 nInitSize )
+{
+ ImplInitSize(nInitSize, false);
+}
+
+ImplPolygon::ImplPolygon( const ImplPolygon& rImpPoly )
+{
+ if ( rImpPoly.mnPoints )
+ {
+ mxPointAry.reset(new Point[rImpPoly.mnPoints]);
+ memcpy(mxPointAry.get(), rImpPoly.mxPointAry.get(), rImpPoly.mnPoints * sizeof(Point));
+
+ if( rImpPoly.mxFlagAry )
+ {
+ mxFlagAry.reset(new PolyFlags[rImpPoly.mnPoints]);
+ memcpy(mxFlagAry.get(), rImpPoly.mxFlagAry.get(), rImpPoly.mnPoints);
+ }
+ }
+
+ mnPoints = rImpPoly.mnPoints;
+}
+
+ImplPolygon::ImplPolygon( sal_uInt16 nInitSize, const Point* pInitAry, const PolyFlags* pInitFlags )
+{
+ if ( nInitSize )
+ {
+ mxPointAry.reset(new Point[nInitSize]);
+ memcpy(mxPointAry.get(), pInitAry, nInitSize * sizeof(Point));
+
+ if( pInitFlags )
+ {
+ mxFlagAry.reset(new PolyFlags[nInitSize]);
+ memcpy(mxFlagAry.get(), pInitFlags, nInitSize);
+ }
+ }
+
+ mnPoints = nInitSize;
+}
+
+ImplPolygon::ImplPolygon( const tools::Rectangle& rRect )
+{
+ if ( !rRect.IsEmpty() )
+ {
+ ImplInitSize(5);
+ mxPointAry[0] = rRect.TopLeft();
+ mxPointAry[1] = rRect.TopRight();
+ mxPointAry[2] = rRect.BottomRight();
+ mxPointAry[3] = rRect.BottomLeft();
+ mxPointAry[4] = rRect.TopLeft();
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ if ( !rRect.IsEmpty() )
+ {
+ tools::Rectangle aRect( rRect );
+ aRect.Normalize(); // SJ: i9140
+
+ nHorzRound = std::min( nHorzRound, static_cast<sal_uInt32>(std::abs( aRect.GetWidth() >> 1 )) );
+ nVertRound = std::min( nVertRound, static_cast<sal_uInt32>(std::abs( aRect.GetHeight() >> 1 )) );
+
+ if( !nHorzRound && !nVertRound )
+ {
+ ImplInitSize(5);
+ mxPointAry[0] = aRect.TopLeft();
+ mxPointAry[1] = aRect.TopRight();
+ mxPointAry[2] = aRect.BottomRight();
+ mxPointAry[3] = aRect.BottomLeft();
+ mxPointAry[4] = aRect.TopLeft();
+ }
+ else
+ {
+ const Point aTL( aRect.Left() + nHorzRound, aRect.Top() + nVertRound );
+ const Point aTR( aRect.Right() - nHorzRound, aRect.Top() + nVertRound );
+ const Point aBR( aRect.Right() - nHorzRound, aRect.Bottom() - nVertRound );
+ const Point aBL( aRect.Left() + nHorzRound, aRect.Bottom() - nVertRound );
+ tools::Polygon aEllipsePoly( Point(), nHorzRound, nVertRound );
+ sal_uInt16 i, nEnd, nSize4 = aEllipsePoly.GetSize() >> 2;
+
+ ImplInitSize(aEllipsePoly.GetSize() + 1);
+
+ const Point* pSrcAry = aEllipsePoly.GetConstPointAry();
+ Point* pDstAry = mxPointAry.get();
+
+ for( i = 0, nEnd = nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aTR;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aTL;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aBL;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aBR;
+
+ pDstAry[ nEnd ] = pDstAry[ 0 ];
+ }
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+{
+ if( nRadX && nRadY )
+ {
+ sal_uInt16 nPoints;
+ // Compute default (depends on size)
+ tools::Long nRadXY;
+ const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
+ if (!bOverflow)
+ {
+ nPoints = std::clamp(
+ ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
+ sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
+ 32.0, 256.0 );
+ }
+ else
+ {
+ nPoints = 256;
+ }
+
+ if( ( nRadX > 32 ) && ( nRadY > 32 ) && ( nRadX + nRadY ) < 8192 )
+ nPoints >>= 1;
+
+ // Ceil number of points until divisible by four
+ nPoints = (nPoints + 3) & ~3;
+ ImplInitSize(nPoints);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints2 = nPoints >> 1;
+ sal_uInt16 nPoints4 = nPoints >> 2;
+ double nAngle;
+ double nAngleStep = M_PI_2 / ( nPoints4 - 1 );
+
+ for( i=0, nAngle = 0.0; i < nPoints4; i++, nAngle += nAngleStep )
+ {
+ tools::Long nX = FRound( nRadX * cos( nAngle ) );
+ tools::Long nY = FRound( -nRadY * sin( nAngle ) );
+
+ Point* pPt = &(mxPointAry[i]);
+ pPt->setX( nX + rCenter.X() );
+ pPt->setY( nY + rCenter.Y() );
+ pPt = &(mxPointAry[nPoints2-i-1]);
+ pPt->setX( -nX + rCenter.X() );
+ pPt->setY( nY + rCenter.Y() );
+ pPt = &(mxPointAry[i+nPoints2]);
+ pPt->setX( -nX + rCenter.X() );
+ pPt->setY( -nY + rCenter.Y() );
+ pPt = &(mxPointAry[nPoints-i-1]);
+ pPt->setX( nX + rCenter.X() );
+ pPt->setY( -nY + rCenter.Y() );
+ }
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd,
+ PolyStyle eStyle, const bool bClockWiseArcDirection)
+{
+ const auto nWidth = rBound.GetWidth();
+ const auto nHeight = rBound.GetHeight();
+
+ if ((nWidth != 0) && (nHeight != 0))
+ {
+ const Point aCenter(rBound.Center());
+ // tdf#142268 Get Top Left corner of rectangle (the rectangle is not always correctly created)
+ const auto aBoundLeft = rBound.Left() < aCenter.X() ? rBound.Left() : rBound.Right();
+ const auto aBoundTop = rBound.Top() < aCenter.Y() ? rBound.Top() : rBound.Bottom();
+ const auto nRadX = o3tl::saturating_sub(aCenter.X(), aBoundLeft);
+ const auto nRadY = o3tl::saturating_sub(aCenter.Y(), aBoundTop);
+ sal_uInt16 nPoints;
+
+ tools::Long nRadXY;
+ const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
+ if (!bOverflow)
+ {
+ nPoints = std::clamp(
+ ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
+ sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
+ 32.0, 256.0 );
+ }
+ else
+ {
+ nPoints = 256;
+ }
+
+
+ if (nRadX > 32 && nRadY > 32 && o3tl::saturating_add(nRadX, nRadY) < 8192)
+ nPoints >>= 1;
+
+ // compute threshold
+ const double fRadX = nRadX;
+ const double fRadY = nRadY;
+ const double fCenterX = aCenter.X();
+ const double fCenterY = aCenter.Y();
+ double fStart = ImplGetParameter( aCenter, rStart, fRadX, fRadY );
+ double fEnd = ImplGetParameter( aCenter, rEnd, fRadX, fRadY );
+ double fDiff = fEnd - fStart;
+ double fStep;
+ sal_uInt16 nStart;
+ sal_uInt16 nEnd;
+
+ if (bClockWiseArcDirection == false)
+ {
+ // #i73608# If startPoint is equal to endPoint, then draw full circle instead of nothing (as Metafiles spec)
+ if (fDiff <= 0.)
+ fDiff += 2. * M_PI;
+ }
+ else
+ {
+ fDiff = (2. * M_PI) - fDiff;
+ if (fDiff > 2. * M_PI)
+ fDiff -= 2. * M_PI;
+ }
+
+ // Proportionally shrink number of points( fDiff / (2PI) );
+ nPoints = std::max(static_cast<sal_uInt16>((fDiff / (2. * M_PI)) * nPoints), sal_uInt16(16));
+ fStep = fDiff / (nPoints - 1);
+ if (bClockWiseArcDirection == true)
+ fStep = -fStep;
+
+ if (PolyStyle::Pie == eStyle)
+ {
+ const Point aCenter2(FRound(fCenterX), FRound(fCenterY));
+
+ nStart = 1;
+ nEnd = nPoints + 1;
+ ImplInitSize(nPoints + 2);
+ mxPointAry[0] = aCenter2;
+ mxPointAry[nEnd] = aCenter2;
+ }
+ else
+ {
+ ImplInitSize( ( PolyStyle::Chord == eStyle ) ? ( nPoints + 1 ) : nPoints );
+ nStart = 0;
+ nEnd = nPoints;
+ }
+
+ for(; nStart < nEnd; nStart++, fStart += fStep )
+ {
+ Point& rPt = mxPointAry[nStart];
+
+ rPt.setX( FRound( fCenterX + fRadX * cos( fStart ) ) );
+ rPt.setY( FRound( fCenterY - fRadY * sin( fStart ) ) );
+ }
+
+ if( PolyStyle::Chord == eStyle )
+ mxPointAry[nPoints] = mxPointAry[0];
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const Point& rBezPt1, const Point& rCtrlPt1,
+ const Point& rBezPt2, const Point& rCtrlPt2, sal_uInt16 nPoints )
+{
+ nPoints = ( 0 == nPoints ) ? 25 : ( ( nPoints < 2 ) ? 2 : nPoints );
+
+ const double fInc = 1.0 / ( nPoints - 1 );
+ double fK_1 = 0.0, fK1_1 = 1.0;
+ double fK_2, fK_3, fK1_2, fK1_3;
+ const double fX0 = rBezPt1.X();
+ const double fY0 = rBezPt1.Y();
+ const double fX1 = 3.0 * rCtrlPt1.X();
+ const double fY1 = 3.0 * rCtrlPt1.Y();
+ const double fX2 = 3.0 * rCtrlPt2.X();
+ const double fY2 = 3.0 * rCtrlPt2.Y();
+ const double fX3 = rBezPt2.X();
+ const double fY3 = rBezPt2.Y();
+
+ ImplInitSize(nPoints);
+
+ for( sal_uInt16 i = 0; i < nPoints; i++, fK_1 += fInc, fK1_1 -= fInc )
+ {
+ Point& rPt = mxPointAry[i];
+
+ fK_2 = fK_1;
+ fK_2 *= fK_1;
+ fK_3 = fK_2;
+ fK_3 *= fK_1;
+ fK1_2 = fK1_1;
+ fK1_2 *= fK1_1;
+ fK1_3 = fK1_2;
+ fK1_3 *= fK1_1;
+ double fK12 = fK_1 * fK1_2;
+ double fK21 = fK_2 * fK1_1;
+
+ rPt.setX( FRound( fK1_3 * fX0 + fK12 * fX1 + fK21 * fX2 + fK_3 * fX3 ) );
+ rPt.setY( FRound( fK1_3 * fY0 + fK12 * fY1 + fK21 * fY2 + fK_3 * fY3 ) );
+ }
+}
+
+// constructor to convert from basegfx::B2DPolygon
+// #i76891# Needed to change from adding all control points (even for unused
+// edges) and creating a fixed-size Polygon in the first run to creating the
+// minimal Polygon. This requires a temporary Point- and Flag-Array for curves
+// and a memcopy at ImplPolygon creation, but contains no zero-controlpoints
+// for straight edges.
+ImplPolygon::ImplPolygon(const basegfx::B2DPolygon& rPolygon)
+ : mnPoints(0)
+{
+ const bool bCurve(rPolygon.areControlPointsUsed());
+ const bool bClosed(rPolygon.isClosed());
+ sal_uInt32 nB2DLocalCount(rPolygon.count());
+
+ if(bCurve)
+ {
+ // #127979# Reduce source point count hard to the limit of the tools Polygon
+ if(nB2DLocalCount > ((0x0000ffff / 3) - 1))
+ {
+ OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
+ nB2DLocalCount = ((0x0000ffff / 3) - 1);
+ }
+
+ // calculate target point count
+ const sal_uInt32 nLoopCount(bClosed ? nB2DLocalCount : (nB2DLocalCount ? nB2DLocalCount - 1 : 0 ));
+
+ if(nLoopCount)
+ {
+ // calculate maximum array size and allocate; prepare insert index
+ const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
+ ImplInitSize(static_cast< sal_uInt16 >(nMaxTargetCount), true);
+
+ // prepare insert index and current point
+ sal_uInt32 nArrayInsert(0);
+ basegfx::B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rPolygon.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ // add current point (always) and remember StartPointIndex for evtl. later corrections
+ const Point aStartPoint(FRound(aBezier.getStartPoint().getX()), FRound(aBezier.getStartPoint().getY()));
+ const sal_uInt32 nStartPointIndex(nArrayInsert);
+ mxPointAry[nStartPointIndex] = aStartPoint;
+ mxFlagAry[nStartPointIndex] = PolyFlags::Normal;
+ nArrayInsert++;
+
+ // prepare next segment
+ const sal_uInt32 nNextIndex((a + 1) % nB2DLocalCount);
+ aBezier.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rPolygon.getNextControlPoint(a));
+ aBezier.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
+
+ if(aBezier.isBezier())
+ {
+ // if one is used, add always two control points due to the old schema
+ mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointA().getX()), FRound(aBezier.getControlPointA().getY()));
+ mxFlagAry[nArrayInsert] = PolyFlags::Control;
+ nArrayInsert++;
+
+ mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointB().getX()), FRound(aBezier.getControlPointB().getY()));
+ mxFlagAry[nArrayInsert] = PolyFlags::Control;
+ nArrayInsert++;
+ }
+
+ // test continuity with previous control point to set flag value
+ if(aBezier.getControlPointA() != aBezier.getStartPoint() && (bClosed || a))
+ {
+ const basegfx::B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
+
+ if(basegfx::B2VectorContinuity::C1 == eCont)
+ {
+ mxFlagAry[nStartPointIndex] = PolyFlags::Smooth;
+ }
+ else if(basegfx::B2VectorContinuity::C2 == eCont)
+ {
+ mxFlagAry[nStartPointIndex] = PolyFlags::Symmetric;
+ }
+ }
+
+ // prepare next polygon step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(bClosed)
+ {
+ // add first point again as closing point due to old definition
+ mxPointAry[nArrayInsert] = mxPointAry[0];
+ mxFlagAry[nArrayInsert] = PolyFlags::Normal;
+ nArrayInsert++;
+ }
+ else
+ {
+ // add last point as closing point
+ const basegfx::B2DPoint aClosingPoint(rPolygon.getB2DPoint(nB2DLocalCount - 1));
+ const Point aEnd(FRound(aClosingPoint.getX()), FRound(aClosingPoint.getY()));
+ mxPointAry[nArrayInsert] = aEnd;
+ mxFlagAry[nArrayInsert] = PolyFlags::Normal;
+ nArrayInsert++;
+ }
+
+ DBG_ASSERT(nArrayInsert <= nMaxTargetCount, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)");
+
+ if(nArrayInsert != nMaxTargetCount)
+ {
+ ImplSetSize(static_cast< sal_uInt16 >(nArrayInsert));
+ }
+ }
+ }
+ else
+ {
+ // #127979# Reduce source point count hard to the limit of the tools Polygon
+ if(nB2DLocalCount > (0x0000ffff - 1))
+ {
+ OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
+ nB2DLocalCount = (0x0000ffff - 1);
+ }
+
+ if(nB2DLocalCount)
+ {
+ // point list creation
+ const sal_uInt32 nTargetCount(nB2DLocalCount + (bClosed ? 1 : 0));
+ ImplInitSize(static_cast< sal_uInt16 >(nTargetCount));
+ sal_uInt16 nIndex(0);
+
+ for(sal_uInt32 a(0); a < nB2DLocalCount; a++)
+ {
+ basegfx::B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
+ Point aPoint(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY()));
+ mxPointAry[nIndex++] = aPoint;
+ }
+
+ if(bClosed)
+ {
+ // add first point as closing point
+ mxPointAry[nIndex] = mxPointAry[0];
+ }
+ }
+ }
+}
+
+bool ImplPolygon::operator==( const ImplPolygon& rCandidate) const
+{
+ return mnPoints == rCandidate.mnPoints &&
+ mxFlagAry.get() == rCandidate.mxFlagAry.get() &&
+ mxPointAry.get() == rCandidate.mxPointAry.get();
+}
+
+void ImplPolygon::ImplInitSize(sal_uInt16 nInitSize, bool bFlags)
+{
+ if (nInitSize)
+ {
+ mxPointAry.reset(new Point[nInitSize]);
+ }
+
+ if (bFlags)
+ {
+ mxFlagAry.reset(new PolyFlags[nInitSize]);
+ memset(mxFlagAry.get(), 0, nInitSize);
+ }
+
+ mnPoints = nInitSize;
+}
+
+void ImplPolygon::ImplSetSize( sal_uInt16 nNewSize, bool bResize )
+{
+ if( mnPoints == nNewSize )
+ return;
+
+ std::unique_ptr<Point[]> xNewAry;
+
+ if (nNewSize)
+ {
+ const std::size_t nNewSz(static_cast<std::size_t>(nNewSize)*sizeof(Point));
+ xNewAry.reset(new Point[nNewSize]);
+
+ if ( bResize )
+ {
+ // Copy the old points
+ if ( mnPoints < nNewSize )
+ {
+ // New points are already implicitly initialized to zero
+ const std::size_t nOldSz(mnPoints * sizeof(Point));
+ if (mxPointAry)
+ memcpy(xNewAry.get(), mxPointAry.get(), nOldSz);
+ }
+ else
+ {
+ if (mxPointAry)
+ memcpy(xNewAry.get(), mxPointAry.get(), nNewSz);
+ }
+ }
+ }
+
+ mxPointAry = std::move(xNewAry);
+
+ // take FlagArray into account, if applicable
+ if( mxFlagAry )
+ {
+ std::unique_ptr<PolyFlags[]> xNewFlagAry;
+
+ if( nNewSize )
+ {
+ xNewFlagAry.reset(new PolyFlags[nNewSize]);
+
+ if( bResize )
+ {
+ // copy the old flags
+ if ( mnPoints < nNewSize )
+ {
+ // initialize new flags to zero
+ memset(xNewFlagAry.get() + mnPoints, 0, nNewSize-mnPoints);
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), mnPoints);
+ }
+ else
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), nNewSize);
+ }
+ }
+
+ mxFlagAry = std::move(xNewFlagAry);
+ }
+
+ mnPoints = nNewSize;
+}
+
+bool ImplPolygon::ImplSplit( sal_uInt16 nPos, sal_uInt16 nSpace, ImplPolygon const * pInitPoly )
+{
+ //Can't fit this in :-(, throw ?
+ if (mnPoints + nSpace > USHRT_MAX)
+ {
+ SAL_WARN("tools", "Polygon needs " << mnPoints + nSpace << " points, but only " << USHRT_MAX << " possible");
+ return false;
+ }
+
+ const sal_uInt16 nNewSize = mnPoints + nSpace;
+ const std::size_t nSpaceSize = static_cast<std::size_t>(nSpace) * sizeof(Point);
+
+ if( nPos >= mnPoints )
+ {
+ // Append at the back
+ nPos = mnPoints;
+ ImplSetSize( nNewSize );
+
+ if( pInitPoly )
+ {
+ memcpy(mxPointAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize);
+
+ if (pInitPoly->mxFlagAry)
+ memcpy(mxFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace);
+ }
+ }
+ else
+ {
+ const sal_uInt16 nSecPos = nPos + nSpace;
+ const sal_uInt16 nRest = mnPoints - nPos;
+
+ std::unique_ptr<Point[]> xNewAry(new Point[nNewSize]);
+ memcpy(xNewAry.get(), mxPointAry.get(), nPos * sizeof(Point));
+
+ if( pInitPoly )
+ memcpy(xNewAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize);
+
+ memcpy(xNewAry.get() + nSecPos, mxPointAry.get() + nPos, nRest * sizeof(Point));
+ mxPointAry = std::move(xNewAry);
+
+ // consider FlagArray
+ if (mxFlagAry)
+ {
+ std::unique_ptr<PolyFlags[]> xNewFlagAry(new PolyFlags[nNewSize]);
+
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), nPos);
+
+ if (pInitPoly && pInitPoly->mxFlagAry)
+ memcpy(xNewFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace);
+ else
+ memset(xNewFlagAry.get() + nPos, 0, nSpace);
+
+ memcpy(xNewFlagAry.get() + nSecPos, mxFlagAry.get() + nPos, nRest);
+ mxFlagAry = std::move(xNewFlagAry);
+ }
+
+ mnPoints = nNewSize;
+ }
+
+ return true;
+}
+
+void ImplPolygon::ImplCreateFlagArray()
+{
+ if (!mxFlagAry)
+ {
+ mxFlagAry.reset(new PolyFlags[mnPoints]);
+ memset(mxFlagAry.get(), 0, mnPoints);
+ }
+}
+
+namespace {
+
+class ImplPointFilter
+{
+public:
+ virtual void LastPoint() = 0;
+ virtual void Input( const Point& rPoint ) = 0;
+
+protected:
+ ~ImplPointFilter() {}
+};
+
+class ImplPolygonPointFilter : public ImplPointFilter
+{
+ ImplPolygon maPoly;
+ sal_uInt16 mnSize;
+public:
+ explicit ImplPolygonPointFilter(sal_uInt16 nDestSize)
+ : maPoly(nDestSize)
+ , mnSize(0)
+ {
+ }
+
+ virtual ~ImplPolygonPointFilter()
+ {
+ }
+
+ virtual void LastPoint() override;
+ virtual void Input( const Point& rPoint ) override;
+
+ ImplPolygon& get() { return maPoly; }
+};
+
+}
+
+void ImplPolygonPointFilter::Input( const Point& rPoint )
+{
+ if ( !mnSize || (rPoint != maPoly.mxPointAry[mnSize-1]) )
+ {
+ mnSize++;
+ if ( mnSize > maPoly.mnPoints )
+ maPoly.ImplSetSize( mnSize );
+ maPoly.mxPointAry[mnSize-1] = rPoint;
+ }
+}
+
+void ImplPolygonPointFilter::LastPoint()
+{
+ if ( mnSize < maPoly.mnPoints )
+ maPoly.ImplSetSize( mnSize );
+};
+
+namespace {
+
+class ImplEdgePointFilter : public ImplPointFilter
+{
+ Point maFirstPoint;
+ Point maLastPoint;
+ ImplPointFilter& mrNextFilter;
+ const tools::Long mnLow;
+ const tools::Long mnHigh;
+ const int mnEdge;
+ int mnLastOutside;
+ bool mbFirst;
+
+public:
+ ImplEdgePointFilter( int nEdge, tools::Long nLow, tools::Long nHigh,
+ ImplPointFilter& rNextFilter ) :
+ mrNextFilter( rNextFilter ),
+ mnLow( nLow ),
+ mnHigh( nHigh ),
+ mnEdge( nEdge ),
+ mnLastOutside( 0 ),
+ mbFirst( true )
+ {
+ }
+
+ virtual ~ImplEdgePointFilter() {}
+
+ Point EdgeSection( const Point& rPoint, int nEdge ) const;
+ int VisibleSide( const Point& rPoint ) const;
+ bool IsPolygon() const
+ { return maFirstPoint == maLastPoint; }
+
+ virtual void Input( const Point& rPoint ) override;
+ virtual void LastPoint() override;
+};
+
+}
+
+inline int ImplEdgePointFilter::VisibleSide( const Point& rPoint ) const
+{
+ if ( mnEdge & EDGE_HORZ )
+ {
+ return rPoint.X() < mnLow ? EDGE_LEFT :
+ rPoint.X() > mnHigh ? EDGE_RIGHT : 0;
+ }
+ else
+ {
+ return rPoint.Y() < mnLow ? EDGE_TOP :
+ rPoint.Y() > mnHigh ? EDGE_BOTTOM : 0;
+ }
+}
+
+Point ImplEdgePointFilter::EdgeSection( const Point& rPoint, int nEdge ) const
+{
+ tools::Long lx = maLastPoint.X();
+ tools::Long ly = maLastPoint.Y();
+ tools::Long md = rPoint.X() - lx;
+ tools::Long mn = rPoint.Y() - ly;
+ tools::Long nNewX;
+ tools::Long nNewY;
+
+ if ( nEdge & EDGE_VERT )
+ {
+ nNewY = (nEdge == EDGE_TOP) ? mnLow : mnHigh;
+ tools::Long dy = nNewY - ly;
+ if ( !md )
+ nNewX = lx;
+ else if ( (LONG_MAX / std::abs(md)) >= std::abs(dy) )
+ nNewX = (dy * md) / mn + lx;
+ else
+ {
+ BigInt ady = dy;
+ ady *= md;
+ if( ady.IsNeg() )
+ if( mn < 0 )
+ ady += mn/2;
+ else
+ ady -= (mn-1)/2;
+ else
+ if( mn < 0 )
+ ady -= (mn+1)/2;
+ else
+ ady += mn/2;
+ ady /= mn;
+ nNewX = static_cast<tools::Long>(ady) + lx;
+ }
+ }
+ else
+ {
+ nNewX = (nEdge == EDGE_LEFT) ? mnLow : mnHigh;
+ tools::Long dx = nNewX - lx;
+ if ( !mn )
+ nNewY = ly;
+ else if ( (LONG_MAX / std::abs(mn)) >= std::abs(dx) )
+ nNewY = (dx * mn) / md + ly;
+ else
+ {
+ BigInt adx = dx;
+ adx *= mn;
+ if( adx.IsNeg() )
+ if( md < 0 )
+ adx += md/2;
+ else
+ adx -= (md-1)/2;
+ else
+ if( md < 0 )
+ adx -= (md+1)/2;
+ else
+ adx += md/2;
+ adx /= md;
+ nNewY = static_cast<tools::Long>(adx) + ly;
+ }
+ }
+
+ return Point( nNewX, nNewY );
+}
+
+void ImplEdgePointFilter::Input( const Point& rPoint )
+{
+ int nOutside = VisibleSide( rPoint );
+
+ if ( mbFirst )
+ {
+ maFirstPoint = rPoint;
+ mbFirst = false;
+ if ( !nOutside )
+ mrNextFilter.Input( rPoint );
+ }
+ else if ( rPoint == maLastPoint )
+ return;
+ else if ( !nOutside )
+ {
+ if ( mnLastOutside )
+ mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) );
+ mrNextFilter.Input( rPoint );
+ }
+ else if ( !mnLastOutside )
+ mrNextFilter.Input( EdgeSection( rPoint, nOutside ) );
+ else if ( nOutside != mnLastOutside )
+ {
+ mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) );
+ mrNextFilter.Input( EdgeSection( rPoint, nOutside ) );
+ }
+
+ maLastPoint = rPoint;
+ mnLastOutside = nOutside;
+}
+
+void ImplEdgePointFilter::LastPoint()
+{
+ if ( !mbFirst )
+ {
+ int nOutside = VisibleSide( maFirstPoint );
+
+ if ( nOutside != mnLastOutside )
+ Input( maFirstPoint );
+ mrNextFilter.LastPoint();
+ }
+}
+
+namespace tools
+{
+
+tools::Polygon Polygon::SubdivideBezier( const tools::Polygon& rPoly )
+{
+ tools::Polygon aPoly;
+
+ // #100127# Use adaptive subdivide instead of fixed 25 segments
+ rPoly.AdaptiveSubdivide( aPoly );
+
+ return aPoly;
+}
+
+Polygon::Polygon() : mpImplPolygon(ImplPolygon())
+{
+}
+
+Polygon::Polygon( sal_uInt16 nSize ) : mpImplPolygon(ImplPolygon(nSize))
+{
+}
+
+Polygon::Polygon( sal_uInt16 nPoints, const Point* pPtAry, const PolyFlags* pFlagAry ) : mpImplPolygon(ImplPolygon(nPoints, pPtAry, pFlagAry))
+{
+}
+
+Polygon::Polygon( const tools::Polygon& rPoly ) : mpImplPolygon(rPoly.mpImplPolygon)
+{
+}
+
+Polygon::Polygon( tools::Polygon&& rPoly) noexcept
+ : mpImplPolygon(std::move(rPoly.mpImplPolygon))
+{
+}
+
+Polygon::Polygon( const tools::Rectangle& rRect ) : mpImplPolygon(ImplPolygon(rRect))
+{
+}
+
+Polygon::Polygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+ : mpImplPolygon(ImplPolygon(rRect, nHorzRound, nVertRound))
+{
+}
+
+Polygon::Polygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+ : mpImplPolygon(ImplPolygon(rCenter, nRadX, nRadY))
+{
+}
+
+Polygon::Polygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd,
+ PolyStyle eStyle, const bool bClockWiseArcDirection)
+ : mpImplPolygon(ImplPolygon(rBound, rStart, rEnd, eStyle, bClockWiseArcDirection))
+{
+}
+
+Polygon::Polygon( const Point& rBezPt1, const Point& rCtrlPt1,
+ const Point& rBezPt2, const Point& rCtrlPt2,
+ sal_uInt16 nPoints ) : mpImplPolygon(ImplPolygon(rBezPt1, rCtrlPt1, rBezPt2, rCtrlPt2, nPoints))
+{
+}
+
+Polygon::~Polygon()
+{
+}
+
+Point * Polygon::GetPointAry()
+{
+ return mpImplPolygon->mxPointAry.get();
+}
+
+const Point* Polygon::GetConstPointAry() const
+{
+ return mpImplPolygon->mxPointAry.get();
+}
+
+const PolyFlags* Polygon::GetConstFlagAry() const
+{
+ return mpImplPolygon->mxFlagAry.get();
+}
+
+void Polygon::SetPoint( const Point& rPt, sal_uInt16 nPos )
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::SetPoint(): nPos >= nPoints" );
+
+ mpImplPolygon->mxPointAry[nPos] = rPt;
+}
+
+void Polygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags )
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::SetFlags(): nPos >= nPoints" );
+
+ // we do only want to create the flag array if there
+ // is at least one flag different to PolyFlags::Normal
+ if ( eFlags != PolyFlags::Normal )
+ {
+ mpImplPolygon->ImplCreateFlagArray();
+ mpImplPolygon->mxFlagAry[ nPos ] = eFlags;
+ }
+}
+
+const Point& Polygon::GetPoint( sal_uInt16 nPos ) const
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::GetPoint(): nPos >= nPoints" );
+
+ return mpImplPolygon->mxPointAry[nPos];
+}
+
+PolyFlags Polygon::GetFlags( sal_uInt16 nPos ) const
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::GetFlags(): nPos >= nPoints" );
+ return mpImplPolygon->mxFlagAry
+ ? mpImplPolygon->mxFlagAry[ nPos ]
+ : PolyFlags::Normal;
+}
+
+bool Polygon::HasFlags() const
+{
+ return bool(mpImplPolygon->mxFlagAry);
+}
+
+bool Polygon::IsRect() const
+{
+ bool bIsRect = false;
+ if (!mpImplPolygon->mxFlagAry)
+ {
+ if ( ( ( mpImplPolygon->mnPoints == 5 ) && ( mpImplPolygon->mxPointAry[ 0 ] == mpImplPolygon->mxPointAry[ 4 ] ) ) ||
+ ( mpImplPolygon->mnPoints == 4 ) )
+ {
+ if ( ( mpImplPolygon->mxPointAry[ 0 ].X() == mpImplPolygon->mxPointAry[ 3 ].X() ) &&
+ ( mpImplPolygon->mxPointAry[ 0 ].Y() == mpImplPolygon->mxPointAry[ 1 ].Y() ) &&
+ ( mpImplPolygon->mxPointAry[ 1 ].X() == mpImplPolygon->mxPointAry[ 2 ].X() ) &&
+ ( mpImplPolygon->mxPointAry[ 2 ].Y() == mpImplPolygon->mxPointAry[ 3 ].Y() ) )
+ bIsRect = true;
+ }
+ }
+ return bIsRect;
+}
+
+void Polygon::SetSize( sal_uInt16 nNewSize )
+{
+ if( nNewSize != mpImplPolygon->mnPoints )
+ {
+ mpImplPolygon->ImplSetSize( nNewSize );
+ }
+}
+
+sal_uInt16 Polygon::GetSize() const
+{
+ return mpImplPolygon->mnPoints;
+}
+
+void Polygon::Clear()
+{
+ mpImplPolygon = ImplType(ImplPolygon());
+}
+
+double Polygon::CalcDistance( sal_uInt16 nP1, sal_uInt16 nP2 ) const
+{
+ DBG_ASSERT( nP1 < mpImplPolygon->mnPoints,
+ "Polygon::CalcDistance(): nPos1 >= nPoints" );
+ DBG_ASSERT( nP2 < mpImplPolygon->mnPoints,
+ "Polygon::CalcDistance(): nPos2 >= nPoints" );
+
+ const Point& rP1 = mpImplPolygon->mxPointAry[ nP1 ];
+ const Point& rP2 = mpImplPolygon->mxPointAry[ nP2 ];
+ const double fDx = rP2.X() - rP1.X();
+ const double fDy = rP2.Y() - rP1.Y();
+
+ return std::hypot( fDx, fDy );
+}
+
+void Polygon::Optimize( PolyOptimizeFlags nOptimizeFlags )
+{
+ sal_uInt16 nSize = mpImplPolygon->mnPoints;
+
+ if( !(bool(nOptimizeFlags) && nSize) )
+ return;
+
+ if( nOptimizeFlags & PolyOptimizeFlags::EDGES )
+ {
+ const tools::Rectangle aBound( GetBoundRect() );
+ const double fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5;
+ const sal_uInt16 nPercent = 50;
+
+ Optimize( PolyOptimizeFlags::NO_SAME );
+ ImplReduceEdges( *this, fArea, nPercent );
+ }
+ else if( nOptimizeFlags & PolyOptimizeFlags::NO_SAME )
+ {
+ tools::Polygon aNewPoly;
+ const Point& rFirst = mpImplPolygon->mxPointAry[ 0 ];
+
+ while( nSize && ( mpImplPolygon->mxPointAry[ nSize - 1 ] == rFirst ) )
+ nSize--;
+
+ if( nSize > 1 )
+ {
+ sal_uInt16 nLast = 0, nNewCount = 1;
+
+ aNewPoly.SetSize( nSize );
+ aNewPoly[ 0 ] = rFirst;
+
+ for( sal_uInt16 i = 1; i < nSize; i++ )
+ {
+ if( mpImplPolygon->mxPointAry[ i ] != mpImplPolygon->mxPointAry[ nLast ])
+ {
+ nLast = i;
+ aNewPoly[ nNewCount++ ] = mpImplPolygon->mxPointAry[ i ];
+ }
+ }
+
+ if( nNewCount == 1 )
+ aNewPoly.Clear();
+ else
+ aNewPoly.SetSize( nNewCount );
+ }
+
+ *this = aNewPoly;
+ }
+
+ nSize = mpImplPolygon->mnPoints;
+
+ if( nSize > 1 )
+ {
+ if( ( nOptimizeFlags & PolyOptimizeFlags::CLOSE ) &&
+ ( mpImplPolygon->mxPointAry[ 0 ] != mpImplPolygon->mxPointAry[ nSize - 1 ] ) )
+ {
+ SetSize( mpImplPolygon->mnPoints + 1 );
+ mpImplPolygon->mxPointAry[ mpImplPolygon->mnPoints - 1 ] = mpImplPolygon->mxPointAry[ 0 ];
+ }
+ }
+}
+
+
+/** Recursively subdivide cubic bezier curve via deCasteljau.
+
+ @param rPoints
+ Output vector, where the subdivided polylines are written to.
+
+ @param d
+ Squared difference of curve to a straight line
+
+ @param P*
+ Exactly four points, interpreted as support and control points of
+ a cubic bezier curve. Must be in device coordinates, since stop
+ criterion is based on the following assumption: the device has a
+ finite resolution, it is thus sufficient to stop subdivision if the
+ curve does not deviate more than one pixel from a straight line.
+
+*/
+static void ImplAdaptiveSubdivide( std::vector<Point>& rPoints,
+ const double old_d2,
+ int recursionDepth,
+ const double d2,
+ const double P1x, const double P1y,
+ const double P2x, const double P2y,
+ const double P3x, const double P3y,
+ const double P4x, const double P4y )
+{
+ // Hard limit on recursion depth, empiric number.
+ enum {maxRecursionDepth=128};
+
+ // Perform bezier flatness test (lecture notes from R. Schaback,
+ // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
+
+ // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
+ // 0<=j<=n
+
+ // What is calculated here is an upper bound to the distance from
+ // a line through b_0 and b_3 (P1 and P4 in our notation) and the
+ // curve. We can drop 0 and n from the running indices, since the
+ // argument of max becomes zero for those cases.
+ const double fJ1x( P2x - P1x - 1.0/3.0*(P4x - P1x) );
+ const double fJ1y( P2y - P1y - 1.0/3.0*(P4y - P1y) );
+ const double fJ2x( P3x - P1x - 2.0/3.0*(P4x - P1x) );
+ const double fJ2y( P3y - P1y - 2.0/3.0*(P4y - P1y) );
+ const double distance2( ::std::max( fJ1x*fJ1x + fJ1y*fJ1y,
+ fJ2x*fJ2x + fJ2y*fJ2y) );
+
+ // stop if error measure does not improve anymore. This is a
+ // safety guard against floating point inaccuracies.
+ // stop at recursion level 128. This is a safety guard against
+ // floating point inaccuracies.
+ // stop if distance from line is guaranteed to be bounded by d
+ if( old_d2 > d2 &&
+ recursionDepth < maxRecursionDepth &&
+ distance2 >= d2 &&
+ rPoints.size() < SAL_MAX_UINT16 )
+ {
+ // deCasteljau bezier arc, split at t=0.5
+ // Foley/vanDam, p. 508
+ const double L1x( P1x ), L1y( P1y );
+ const double L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 );
+ const double Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 );
+ const double L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 );
+ const double R4x( P4x ), R4y( P4y );
+ const double R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 );
+ const double R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 );
+ const double R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 );
+ const double L4x( R1x ), L4y( R1y );
+
+ // subdivide further
+ ++recursionDepth;
+ ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y);
+ ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y);
+ }
+ else
+ {
+ // requested resolution reached.
+ // Add end points to output iterator.
+ // order is preserved, since this is so to say depth first traversal.
+ rPoints.push_back(Point(FRound(P1x), FRound(P1y)));
+ }
+}
+
+void Polygon::AdaptiveSubdivide( Polygon& rResult, const double d ) const
+{
+ if (!mpImplPolygon->mxFlagAry)
+ {
+ rResult = *this;
+ }
+ else
+ {
+ sal_uInt16 i;
+ sal_uInt16 nPts( GetSize() );
+ ::std::vector< Point > aPoints;
+ aPoints.reserve( nPts );
+
+ for(i=0; i<nPts;)
+ {
+ if( ( i + 3 ) < nPts )
+ {
+ PolyFlags P1( mpImplPolygon->mxFlagAry[ i ] );
+ PolyFlags P4( mpImplPolygon->mxFlagAry[ i + 3 ] );
+
+ if( ( PolyFlags::Normal == P1 || PolyFlags::Smooth == P1 || PolyFlags::Symmetric == P1 ) &&
+ ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 1 ] ) &&
+ ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 2 ] ) &&
+ ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
+ {
+ ImplAdaptiveSubdivide( aPoints, d*d+1.0, 0, d*d,
+ mpImplPolygon->mxPointAry[ i ].X(), mpImplPolygon->mxPointAry[ i ].Y(),
+ mpImplPolygon->mxPointAry[ i+1 ].X(), mpImplPolygon->mxPointAry[ i+1 ].Y(),
+ mpImplPolygon->mxPointAry[ i+2 ].X(), mpImplPolygon->mxPointAry[ i+2 ].Y(),
+ mpImplPolygon->mxPointAry[ i+3 ].X(), mpImplPolygon->mxPointAry[ i+3 ].Y() );
+ i += 3;
+ continue;
+ }
+ }
+
+ aPoints.push_back(mpImplPolygon->mxPointAry[i++]);
+
+ if (aPoints.size() >= SAL_MAX_UINT16)
+ {
+ OSL_ENSURE(aPoints.size() < SAL_MAX_UINT16,
+ "Polygon::AdaptiveSubdivision created polygon too many points;"
+ " using original polygon instead");
+
+ // The resulting polygon can not hold all the points
+ // that we have created so far. Stop the subdivision
+ // and return a copy of the unmodified polygon.
+ rResult = *this;
+ return;
+ }
+ }
+
+ // fill result polygon
+ rResult = tools::Polygon( static_cast<sal_uInt16>(aPoints.size()) ); // ensure sufficient size for copy
+ ::std::copy(aPoints.begin(), aPoints.end(), rResult.mpImplPolygon->mxPointAry.get());
+ }
+}
+
+namespace {
+
+class Vector2D
+{
+private:
+ double mfX;
+ double mfY;
+public:
+ explicit Vector2D( const Point& rPoint ) : mfX( rPoint.X() ), mfY( rPoint.Y() ) {};
+ double GetLength() const { return hypot( mfX, mfY ); }
+ Vector2D& operator-=( const Vector2D& rVec ) { mfX -= rVec.mfX; mfY -= rVec.mfY; return *this; }
+ double Scalar( const Vector2D& rVec ) const { return mfX * rVec.mfX + mfY * rVec.mfY ; }
+ Vector2D& Normalize();
+ bool IsPositive( Vector2D const & rVec ) const { return ( mfX * rVec.mfY - mfY * rVec.mfX ) >= 0.0; }
+ bool IsNegative( Vector2D const & rVec ) const { return !IsPositive( rVec ); }
+};
+
+}
+
+Vector2D& Vector2D::Normalize()
+{
+ double fLen = Scalar( *this );
+
+ if( ( fLen != 0.0 ) && ( fLen != 1.0 ) )
+ {
+ fLen = sqrt( fLen );
+ if( fLen != 0.0 )
+ {
+ mfX /= fLen;
+ mfY /= fLen;
+ }
+ }
+
+ return *this;
+}
+
+void Polygon::ImplReduceEdges( tools::Polygon& rPoly, const double& rArea, sal_uInt16 nPercent )
+{
+ const double fBound = 2000.0 * ( 100 - nPercent ) * 0.01;
+ sal_uInt16 nNumNoChange = 0,
+ nNumRuns = 0;
+
+ while( nNumNoChange < 2 )
+ {
+ sal_uInt16 nPntCnt = rPoly.GetSize(), nNewPos = 0;
+ tools::Polygon aNewPoly( nPntCnt );
+ bool bChangeInThisRun = false;
+
+ for( sal_uInt16 n = 0; n < nPntCnt; n++ )
+ {
+ bool bDeletePoint = false;
+
+ if( ( n + nNumRuns ) % 2 )
+ {
+ sal_uInt16 nIndPrev = !n ? nPntCnt - 1 : n - 1;
+ sal_uInt16 nIndPrevPrev = !nIndPrev ? nPntCnt - 1 : nIndPrev - 1;
+ sal_uInt16 nIndNext = ( n == nPntCnt-1 ) ? 0 : n + 1;
+ sal_uInt16 nIndNextNext = ( nIndNext == nPntCnt - 1 ) ? 0 : nIndNext + 1;
+ Vector2D aVec1( rPoly[ nIndPrev ] ); aVec1 -= Vector2D(rPoly[ nIndPrevPrev ]);
+ Vector2D aVec2( rPoly[ n ] ); aVec2 -= Vector2D(rPoly[ nIndPrev ]);
+ Vector2D aVec3( rPoly[ nIndNext ] ); aVec3 -= Vector2D(rPoly[ n ]);
+ Vector2D aVec4( rPoly[ nIndNextNext ] ); aVec4 -= Vector2D(rPoly[ nIndNext ]);
+ double fDist1 = aVec1.GetLength(), fDist2 = aVec2.GetLength();
+ double fDist3 = aVec3.GetLength(), fDist4 = aVec4.GetLength();
+ double fTurnB = aVec2.Normalize().Scalar( aVec3.Normalize() );
+
+ if( fabs( fTurnB ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnB ) > ( 1.0 - SMALL_DVALUE ) )
+ bDeletePoint = true;
+ else
+ {
+ Vector2D aVecB( rPoly[ nIndNext ] );
+ aVecB -= Vector2D(rPoly[ nIndPrev ] );
+ double fDistB = aVecB.GetLength();
+ double fLenWithB = fDist2 + fDist3;
+ double fLenFact = ( fDistB != 0.0 ) ? fLenWithB / fDistB : 1.0;
+ double fTurnPrev = aVec1.Normalize().Scalar( aVec2 );
+ double fTurnNext = aVec3.Scalar( aVec4.Normalize() );
+ double fGradPrev, fGradB, fGradNext;
+
+ if( fabs( fTurnPrev ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnPrev ) > ( 1.0 - SMALL_DVALUE ) )
+ fGradPrev = 0.0;
+ else
+ fGradPrev = basegfx::rad2deg(acos(fTurnPrev)) * (aVec1.IsNegative(aVec2) ? -1 : 1);
+
+ fGradB = basegfx::rad2deg(acos(fTurnB)) * (aVec2.IsNegative(aVec3) ? -1 : 1);
+
+ if( fabs( fTurnNext ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnNext ) > ( 1.0 - SMALL_DVALUE ) )
+ fGradNext = 0.0;
+ else
+ fGradNext = basegfx::rad2deg(acos(fTurnNext)) * (aVec3.IsNegative(aVec4) ? -1 : 1);
+
+ if( ( fGradPrev > 0.0 && fGradB < 0.0 && fGradNext > 0.0 ) ||
+ ( fGradPrev < 0.0 && fGradB > 0.0 && fGradNext < 0.0 ) )
+ {
+ if( ( fLenFact < ( FSQRT2 + SMALL_DVALUE ) ) &&
+ ( ( ( fDist1 + fDist4 ) / ( fDist2 + fDist3 ) ) * 2000.0 ) > fBound )
+ {
+ bDeletePoint = true;
+ }
+ }
+ else
+ {
+ double fRelLen = 1.0 - sqrt( fDistB / rArea );
+
+ if( fRelLen < 0.0 )
+ fRelLen = 0.0;
+ else if( fRelLen > 1.0 )
+ fRelLen = 1.0;
+
+ if( ( std::round( ( fLenFact - 1.0 ) * 1000000.0 ) < fBound ) &&
+ ( fabs( fGradB ) <= ( fRelLen * fBound * 0.01 ) ) )
+ {
+ bDeletePoint = true;
+ }
+ }
+ }
+ }
+
+ if( !bDeletePoint )
+ aNewPoly[ nNewPos++ ] = rPoly[ n ];
+ else
+ bChangeInThisRun = true;
+ }
+
+ if( bChangeInThisRun && nNewPos )
+ {
+ aNewPoly.SetSize( nNewPos );
+ rPoly = aNewPoly;
+ nNumNoChange = 0;
+ }
+ else
+ nNumNoChange++;
+
+ nNumRuns++;
+ }
+}
+
+void Polygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ // This check is required for DrawEngine
+ if ( !nHorzMove && !nVertMove )
+ return;
+
+ // Move points
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ Point& rPt = mpImplPolygon->mxPointAry[i];
+ rPt.AdjustX(nHorzMove );
+ rPt.AdjustY(nVertMove );
+ }
+}
+
+void Polygon::Translate(const Point& rTrans)
+{
+ for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ mpImplPolygon->mxPointAry[ i ] += rTrans;
+}
+
+void Polygon::Scale( double fScaleX, double fScaleY )
+{
+ for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ {
+ Point& rPnt = mpImplPolygon->mxPointAry[i];
+ rPnt.setX( static_cast<tools::Long>( fScaleX * rPnt.X() ) );
+ rPnt.setY( static_cast<tools::Long>( fScaleY * rPnt.Y() ) );
+ }
+}
+
+void Polygon::Rotate( const Point& rCenter, Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+
+ if( nAngle10 )
+ {
+ const double fAngle = toRadians(nAngle10);
+ Rotate( rCenter, sin( fAngle ), cos( fAngle ) );
+ }
+}
+
+void Polygon::Rotate( const Point& rCenter, double fSin, double fCos )
+{
+ tools::Long nCenterX = rCenter.X();
+ tools::Long nCenterY = rCenter.Y();
+
+ for( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ {
+ Point& rPt = mpImplPolygon->mxPointAry[ i ];
+
+ const tools::Long nX = rPt.X() - nCenterX;
+ const tools::Long nY = rPt.Y() - nCenterY;
+ rPt.setX( FRound(fCos * nX + fSin * nY + nCenterX) );
+ rPt.setY( FRound(-(fSin * nX - fCos * nY - nCenterY)) );
+ }
+}
+
+void Polygon::Clip( const tools::Rectangle& rRect )
+{
+ // This algorithm is broken for bezier curves, they would get converted to lines.
+ // Use PolyPolygon::Clip().
+ assert( !HasFlags());
+
+ // #105251# Normalize rect before edge filtering
+ tools::Rectangle aJustifiedRect( rRect );
+ aJustifiedRect.Normalize();
+
+ sal_uInt16 nSourceSize = mpImplPolygon->mnPoints;
+ ImplPolygonPointFilter aPolygon( nSourceSize );
+ ImplEdgePointFilter aHorzFilter( EDGE_HORZ, aJustifiedRect.Left(), aJustifiedRect.Right(),
+ aPolygon );
+ ImplEdgePointFilter aVertFilter( EDGE_VERT, aJustifiedRect.Top(), aJustifiedRect.Bottom(),
+ aHorzFilter );
+
+ for ( sal_uInt16 i = 0; i < nSourceSize; i++ )
+ aVertFilter.Input( mpImplPolygon->mxPointAry[i] );
+ if ( aVertFilter.IsPolygon() )
+ aVertFilter.LastPoint();
+ else
+ aPolygon.LastPoint();
+
+ mpImplPolygon = ImplType(aPolygon.get());
+}
+
+tools::Rectangle Polygon::GetBoundRect() const
+{
+ // Removing the assert. Bezier curves have the attribute that each single
+ // curve segment defined by four points can not exit the four-point polygon
+ // defined by that points. This allows to say that the curve segment can also
+ // never leave the Range of its defining points.
+ // The result is that Polygon::GetBoundRect() may not create the minimal
+ // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes),
+ // but will always create a valid BoundRect, at least as long as this method
+ // 'blindly' travels over all points, including control points.
+
+ // DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" );
+
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ if( ! nCount )
+ return tools::Rectangle();
+
+ tools::Long nXMin, nXMax, nYMin, nYMax;
+
+ const Point& pFirstPt = mpImplPolygon->mxPointAry[0];
+ nXMin = nXMax = pFirstPt.X();
+ nYMin = nYMax = pFirstPt.Y();
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ const Point& rPt = mpImplPolygon->mxPointAry[i];
+
+ if (rPt.X() < nXMin)
+ nXMin = rPt.X();
+ if (rPt.X() > nXMax)
+ nXMax = rPt.X();
+ if (rPt.Y() < nYMin)
+ nYMin = rPt.Y();
+ if (rPt.Y() > nYMax)
+ nYMax = rPt.Y();
+ }
+
+ return tools::Rectangle( nXMin, nYMin, nXMax, nYMax );
+}
+
+bool Polygon::Contains( const Point& rPoint ) const
+{
+ DBG_ASSERT( !mpImplPolygon->mxFlagAry, "IsInside could fail with beziers!" );
+
+ const tools::Rectangle aBound( GetBoundRect() );
+ const Line aLine( rPoint, Point( aBound.Right() + 100, rPoint.Y() ) );
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ sal_uInt16 nPCounter = 0;
+
+ if ( ( nCount > 2 ) && aBound.Contains( rPoint ) )
+ {
+ Point aPt1( mpImplPolygon->mxPointAry[ 0 ] );
+ Point aIntersection;
+ Point aLastIntersection;
+
+ while ( ( aPt1 == mpImplPolygon->mxPointAry[ nCount - 1 ] ) && ( nCount > 3 ) )
+ nCount--;
+
+ for ( sal_uInt16 i = 1; i <= nCount; i++ )
+ {
+ const Point& rPt2 = mpImplPolygon->mxPointAry[ ( i < nCount ) ? i : 0 ];
+
+ if ( aLine.Intersection( Line( aPt1, rPt2 ), aIntersection ) )
+ {
+ // This avoids insertion of double intersections
+ if ( nPCounter )
+ {
+ if ( aIntersection != aLastIntersection )
+ {
+ aLastIntersection = aIntersection;
+ nPCounter++;
+ }
+ }
+ else
+ {
+ aLastIntersection = aIntersection;
+ nPCounter++;
+ }
+ }
+
+ aPt1 = rPt2;
+ }
+ }
+
+ // is inside, if number of intersection points is odd
+ return ( ( nPCounter & 1 ) == 1 );
+}
+
+void Polygon::Insert( sal_uInt16 nPos, const Point& rPt )
+{
+ if( nPos >= mpImplPolygon->mnPoints )
+ nPos = mpImplPolygon->mnPoints;
+
+ if (mpImplPolygon->ImplSplit(nPos, 1))
+ mpImplPolygon->mxPointAry[ nPos ] = rPt;
+}
+
+void Polygon::Insert( sal_uInt16 nPos, const tools::Polygon& rPoly )
+{
+ const sal_uInt16 nInsertCount = rPoly.mpImplPolygon->mnPoints;
+
+ if( nInsertCount )
+ {
+ if( nPos >= mpImplPolygon->mnPoints )
+ nPos = mpImplPolygon->mnPoints;
+
+ if (rPoly.mpImplPolygon->mxFlagAry)
+ mpImplPolygon->ImplCreateFlagArray();
+
+ mpImplPolygon->ImplSplit( nPos, nInsertCount, rPoly.mpImplPolygon.get() );
+ }
+}
+
+Point& Polygon::operator[]( sal_uInt16 nPos )
+{
+ assert( nPos < mpImplPolygon->mnPoints );
+
+ return mpImplPolygon->mxPointAry[nPos];
+}
+
+tools::Polygon& Polygon::operator=( const tools::Polygon& rPoly )
+{
+ mpImplPolygon = rPoly.mpImplPolygon;
+ return *this;
+}
+
+tools::Polygon& Polygon::operator=( tools::Polygon&& rPoly ) noexcept
+{
+ mpImplPolygon = std::move(rPoly.mpImplPolygon);
+ return *this;
+}
+
+bool Polygon::operator==( const tools::Polygon& rPoly ) const
+{
+ return (mpImplPolygon == rPoly.mpImplPolygon);
+}
+
+bool Polygon::IsEqual( const tools::Polygon& rPoly ) const
+{
+ bool bIsEqual = true;
+ sal_uInt16 i;
+ if ( GetSize() != rPoly.GetSize() )
+ bIsEqual = false;
+ else
+ {
+ for ( i = 0; i < GetSize(); i++ )
+ {
+ if ( ( GetPoint( i ) != rPoly.GetPoint( i ) ) ||
+ ( GetFlags( i ) != rPoly.GetFlags( i ) ) )
+ {
+ bIsEqual = false;
+ break;
+ }
+ }
+ }
+ return bIsEqual;
+}
+
+SvStream& ReadPolygon( SvStream& rIStream, tools::Polygon& rPoly )
+{
+ sal_uInt16 nPoints(0);
+
+ // read all points and create array
+ rIStream.ReadUInt16( nPoints );
+
+ const size_t nMaxRecordsPossible = rIStream.remainingSize() / (2 * sizeof(sal_Int32));
+ if (nPoints > nMaxRecordsPossible)
+ {
+ SAL_WARN("tools", "Polygon claims " << nPoints << " records, but only " << nMaxRecordsPossible << " possible");
+ nPoints = nMaxRecordsPossible;
+ }
+
+ rPoly.mpImplPolygon->ImplSetSize( nPoints, false );
+
+ for (sal_uInt16 i = 0; i < nPoints; i++)
+ {
+ sal_Int32 nTmpX(0), nTmpY(0);
+ rIStream.ReadInt32(nTmpX).ReadInt32(nTmpY);
+ rPoly.mpImplPolygon->mxPointAry[i].setX(nTmpX);
+ rPoly.mpImplPolygon->mxPointAry[i].setY(nTmpY);
+ }
+
+ return rIStream;
+}
+
+SvStream& WritePolygon( SvStream& rOStream, const tools::Polygon& rPoly )
+{
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ // Write number of points
+ rOStream.WriteUInt16( nPoints );
+
+ for (sal_uInt16 i = 0; i < nPoints; i++)
+ {
+ rOStream.WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].X())
+ .WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].Y());
+ }
+
+ return rOStream;
+}
+
+void Polygon::ImplRead( SvStream& rIStream )
+{
+ sal_uInt8 bHasPolyFlags(0);
+
+ ReadPolygon( rIStream, *this );
+ rIStream.ReadUChar( bHasPolyFlags );
+
+ if ( bHasPolyFlags )
+ {
+ mpImplPolygon->mxFlagAry.reset(new PolyFlags[mpImplPolygon->mnPoints]);
+ auto nRead = rIStream.ReadBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints);
+ if (nRead != mpImplPolygon->mnPoints)
+ {
+ SAL_WARN("tools", "Short read");
+ memset(mpImplPolygon->mxFlagAry.get() + nRead, 0, mpImplPolygon->mnPoints - nRead);
+ }
+ }
+}
+
+void Polygon::Read( SvStream& rIStream )
+{
+ VersionCompatRead aCompat(rIStream);
+
+ ImplRead( rIStream );
+}
+
+void Polygon::ImplWrite( SvStream& rOStream ) const
+{
+ bool bHasPolyFlags(mpImplPolygon->mxFlagAry);
+ WritePolygon( rOStream, *this );
+ rOStream.WriteBool(bHasPolyFlags);
+
+ if ( bHasPolyFlags )
+ rOStream.WriteBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints);
+}
+
+void Polygon::Write( SvStream& rOStream ) const
+{
+ VersionCompatWrite aCompat(rOStream, 1);
+
+ ImplWrite( rOStream );
+}
+
+// #i74631#/#i115917# numerical correction method for B2DPolygon
+static void impCorrectContinuity(basegfx::B2DPolygon& roPolygon, sal_uInt32 nIndex, PolyFlags nCFlag)
+{
+ const sal_uInt32 nPointCount(roPolygon.count());
+ OSL_ENSURE(nIndex < nPointCount, "impCorrectContinuity: index access out of range (!)");
+
+ if(nIndex >= nPointCount || (PolyFlags::Smooth != nCFlag && PolyFlags::Symmetric != nCFlag))
+ return;
+
+ if(!roPolygon.isPrevControlPointUsed(nIndex) || !roPolygon.isNextControlPointUsed(nIndex))
+ return;
+
+ // #i115917# Patch from osnola (modified, thanks for showing the problem)
+
+ // The correction is needed because an integer polygon with control points
+ // is converted to double precision. When C1 or C2 is used the involved vectors
+ // may not have the same directions/lengths since these come from integer coordinates
+ // and may have been snapped to different nearest integer coordinates. The snap error
+ // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless,
+ // it needs to be corrected to be able to detect the continuity in this points
+ // correctly.
+
+ // We only have the integer data here (already in double precision form, but no mantissa
+ // used), so the best correction is to use:
+
+ // for C1: The longest vector since it potentially has best preserved the original vector.
+ // Even better the sum of the vectors, weighted by their length. This gives the
+ // normal vector addition to get the vector itself, lengths need to be preserved.
+ // for C2: The mediated vector(s) since both should be the same, but mirrored
+
+ // extract the point and vectors
+ const basegfx::B2DPoint aPoint(roPolygon.getB2DPoint(nIndex));
+ const basegfx::B2DVector aNext(roPolygon.getNextControlPoint(nIndex) - aPoint);
+ const basegfx::B2DVector aPrev(aPoint - roPolygon.getPrevControlPoint(nIndex));
+
+ // calculate common direction vector, normalize
+ const basegfx::B2DVector aDirection(aNext + aPrev);
+ const double fDirectionLen = aDirection.getLength();
+ if (fDirectionLen == 0.0)
+ return;
+
+ if (PolyFlags::Smooth == nCFlag)
+ {
+ // C1: apply common direction vector, preserve individual lengths
+ const double fInvDirectionLen(1.0 / fDirectionLen);
+ roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + (aDirection * (aNext.getLength() * fInvDirectionLen))));
+ roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - (aDirection * (aPrev.getLength() * fInvDirectionLen))));
+ }
+ else // PolyFlags::Symmetric
+ {
+ // C2: get mediated length. Taking half of the unnormalized direction would be
+ // an approximation, but not correct.
+ const double fMedLength((aNext.getLength() + aPrev.getLength()) * (0.5 / fDirectionLen));
+ const basegfx::B2DVector aScaledDirection(aDirection * fMedLength);
+
+ // Bring Direction to correct length and apply
+ roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + aScaledDirection));
+ roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - aScaledDirection));
+ }
+}
+
+// convert to basegfx::B2DPolygon and return
+basegfx::B2DPolygon Polygon::getB2DPolygon() const
+{
+ basegfx::B2DPolygon aRetval;
+ const sal_uInt16 nCount(mpImplPolygon->mnPoints);
+
+ if (nCount)
+ {
+ if (mpImplPolygon->mxFlagAry)
+ {
+ // handling for curves. Add start point
+ const Point aStartPoint(mpImplPolygon->mxPointAry[0]);
+ PolyFlags nPointFlag(mpImplPolygon->mxFlagAry[0]);
+ aRetval.append(basegfx::B2DPoint(aStartPoint.X(), aStartPoint.Y()));
+ Point aControlA, aControlB;
+
+ for(sal_uInt16 a(1); a < nCount;)
+ {
+ bool bControlA(false);
+ bool bControlB(false);
+
+ if(PolyFlags::Control == mpImplPolygon->mxFlagAry[a])
+ {
+ aControlA = mpImplPolygon->mxPointAry[a++];
+ bControlA = true;
+ }
+
+ if(a < nCount && PolyFlags::Control == mpImplPolygon->mxFlagAry[a])
+ {
+ aControlB = mpImplPolygon->mxPointAry[a++];
+ bControlB = true;
+ }
+
+ // assert invalid polygons
+ OSL_ENSURE(bControlA == bControlB, "Polygon::getB2DPolygon: Invalid source polygon (!)");
+
+ if(a < nCount)
+ {
+ const Point aEndPoint(mpImplPolygon->mxPointAry[a]);
+
+ if(bControlA)
+ {
+ // bezier edge, add
+ aRetval.appendBezierSegment(
+ basegfx::B2DPoint(aControlA.X(), aControlA.Y()),
+ basegfx::B2DPoint(aControlB.X(), aControlB.Y()),
+ basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y()));
+
+ impCorrectContinuity(aRetval, aRetval.count() - 2, nPointFlag);
+ }
+ else
+ {
+ // no bezier edge, add end point
+ aRetval.append(basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y()));
+ }
+
+ nPointFlag = mpImplPolygon->mxFlagAry[a++];
+ }
+ }
+
+ // if exist, remove double first/last points, set closed and correct control points
+ basegfx::utils::checkClosed(aRetval);
+
+ if(aRetval.isClosed())
+ {
+ // closeWithGeometryChange did really close, so last point(s) were removed.
+ // Correct the continuity in the changed point
+ impCorrectContinuity(aRetval, 0, mpImplPolygon->mxFlagAry[0]);
+ }
+ }
+ else
+ {
+ // extra handling for non-curves (most-used case) for speedup
+ for(sal_uInt16 a(0); a < nCount; a++)
+ {
+ // get point and add
+ const Point aPoint(mpImplPolygon->mxPointAry[a]);
+ aRetval.append(basegfx::B2DPoint(aPoint.X(), aPoint.Y()));
+ }
+
+ // set closed flag
+ basegfx::utils::checkClosed(aRetval);
+ }
+ }
+
+ return aRetval;
+}
+
+Polygon::Polygon(const basegfx::B2DPolygon& rPolygon) : mpImplPolygon(ImplPolygon(rPolygon))
+{
+}
+
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/poly2.cxx b/tools/source/generic/poly2.cxx
new file mode 100644
index 0000000000..a00f64f41b
--- /dev/null
+++ b/tools/source/generic/poly2.cxx
@@ -0,0 +1,520 @@
+/* -*- 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 <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <poly.h>
+#include <tools/poly.hxx>
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/gen.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+
+namespace tools {
+
+PolyPolygon::PolyPolygon( sal_uInt16 nInitSize )
+ : mpImplPolyPolygon( ImplPolyPolygon( nInitSize ) )
+{
+}
+
+PolyPolygon::PolyPolygon( const tools::Polygon& rPoly )
+ : mpImplPolyPolygon( rPoly )
+{
+}
+PolyPolygon::PolyPolygon( const tools::Rectangle& rRect )
+ : mpImplPolyPolygon( tools::Polygon(rRect) )
+{
+}
+
+PolyPolygon::PolyPolygon( const tools::PolyPolygon& rPolyPoly )
+ : mpImplPolyPolygon( rPolyPoly.mpImplPolyPolygon )
+{
+}
+
+PolyPolygon::PolyPolygon( tools::PolyPolygon&& rPolyPoly ) noexcept
+ : mpImplPolyPolygon( std::move(rPolyPoly.mpImplPolyPolygon) )
+{
+}
+
+PolyPolygon::~PolyPolygon()
+{
+}
+
+void PolyPolygon::Insert( const tools::Polygon& rPoly, sal_uInt16 nPos )
+{
+ assert ( mpImplPolyPolygon->mvPolyAry.size() < MAX_POLYGONS );
+
+ if ( nPos > mpImplPolyPolygon->mvPolyAry.size() )
+ nPos = mpImplPolyPolygon->mvPolyAry.size();
+
+ mpImplPolyPolygon->mvPolyAry.insert(mpImplPolyPolygon->mvPolyAry.begin() + nPos, rPoly);
+}
+
+void PolyPolygon::Remove( sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::Remove(): nPos >= nSize");
+
+ mpImplPolyPolygon->mvPolyAry.erase(mpImplPolyPolygon->mvPolyAry.begin() + nPos);
+}
+
+void PolyPolygon::Replace( const tools::Polygon& rPoly, sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::Replace(): nPos >= nSize");
+
+ mpImplPolyPolygon->mvPolyAry[nPos] = rPoly;
+}
+
+const tools::Polygon& PolyPolygon::GetObject( sal_uInt16 nPos ) const
+{
+ assert(nPos < Count() && "PolyPolygon::GetObject(): nPos >= nSize");
+
+ return mpImplPolyPolygon->mvPolyAry[nPos];
+}
+
+bool PolyPolygon::IsRect() const
+{
+ bool bIsRect = false;
+ if ( Count() == 1 )
+ bIsRect = mpImplPolyPolygon->mvPolyAry[ 0 ].IsRect();
+ return bIsRect;
+}
+
+void PolyPolygon::Clear()
+{
+ mpImplPolyPolygon->mvPolyAry.clear();
+}
+
+void PolyPolygon::Optimize( PolyOptimizeFlags nOptimizeFlags )
+{
+ if(!(bool(nOptimizeFlags) && Count()))
+ return;
+
+ // #115630# ImplDrawHatch does not work with beziers included in the polypolygon, take care of that
+ bool bIsCurve(false);
+
+ for(sal_uInt16 a(0); !bIsCurve && a < Count(); a++)
+ {
+ if((*this)[a].HasFlags())
+ {
+ bIsCurve = true;
+ }
+ }
+
+ if(bIsCurve)
+ {
+ OSL_ENSURE(false, "Optimize does *not* support curves, falling back to AdaptiveSubdivide()...");
+ tools::PolyPolygon aPolyPoly;
+
+ AdaptiveSubdivide(aPolyPoly);
+ aPolyPoly.Optimize(nOptimizeFlags);
+ *this = aPolyPoly;
+ }
+ else
+ {
+ double fArea;
+ const bool bEdges = ( nOptimizeFlags & PolyOptimizeFlags::EDGES ) == PolyOptimizeFlags::EDGES;
+ sal_uInt16 nPercent = 0;
+
+ if( bEdges )
+ {
+ const tools::Rectangle aBound( GetBoundRect() );
+
+ fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5;
+ nPercent = 50;
+ nOptimizeFlags &= ~PolyOptimizeFlags::EDGES;
+ }
+
+ // Optimize polygons
+ for( sal_uInt16 i = 0, nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); i < nPolyCount; i++ )
+ {
+ if( bEdges )
+ {
+ mpImplPolyPolygon->mvPolyAry[ i ].Optimize( PolyOptimizeFlags::NO_SAME );
+ tools::Polygon::ImplReduceEdges( mpImplPolyPolygon->mvPolyAry[ i ], fArea, nPercent );
+ }
+
+ if( bool(nOptimizeFlags) )
+ mpImplPolyPolygon->mvPolyAry[ i ].Optimize( nOptimizeFlags );
+ }
+ }
+}
+
+void PolyPolygon::AdaptiveSubdivide( tools::PolyPolygon& rResult ) const
+{
+ rResult.Clear();
+
+ tools::Polygon aPolygon;
+
+ for( size_t i = 0; i < mpImplPolyPolygon->mvPolyAry.size(); i++ )
+ {
+ mpImplPolyPolygon->mvPolyAry[ i ].AdaptiveSubdivide( aPolygon, 1.0 );
+ rResult.Insert( aPolygon );
+ }
+}
+
+tools::PolyPolygon PolyPolygon::SubdivideBezier( const tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 i, nPolys = rPolyPoly.Count();
+ tools::PolyPolygon aPolyPoly( nPolys );
+ for( i=0; i<nPolys; ++i )
+ aPolyPoly.Insert( tools::Polygon::SubdivideBezier( rPolyPoly.GetObject(i) ) );
+
+ return aPolyPoly;
+}
+
+
+void PolyPolygon::GetIntersection( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
+{
+ ImplDoOperation( rPolyPoly, rResult, PolyClipOp::INTERSECT );
+}
+
+void PolyPolygon::GetUnion( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
+{
+ ImplDoOperation( rPolyPoly, rResult, PolyClipOp::UNION );
+}
+
+void PolyPolygon::ImplDoOperation( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult, PolyClipOp nOperation ) const
+{
+ // Convert to B2DPolyPolygon, temporarily. It might be
+ // advantageous in the future, to have a tools::PolyPolygon adaptor that
+ // just simulates a B2DPolyPolygon here...
+ basegfx::B2DPolyPolygon aMergePolyPolygonA( getB2DPolyPolygon() );
+ basegfx::B2DPolyPolygon aMergePolyPolygonB( rPolyPoly.getB2DPolyPolygon() );
+
+ // normalize the two polypolygons before. Force properly oriented
+ // polygons.
+ aMergePolyPolygonA = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonA );
+ aMergePolyPolygonB = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonB );
+
+ switch( nOperation )
+ {
+ // All code extracted from svx/source/svdraw/svedtv2.cxx
+
+ case PolyClipOp::UNION:
+ {
+ // merge A and B (OR)
+ aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB);
+ break;
+ }
+
+ default:
+ case PolyClipOp::INTERSECT:
+ {
+ // cut poly 1 against polys 2..n (AND)
+ aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB);
+ break;
+ }
+ }
+
+ rResult = tools::PolyPolygon( aMergePolyPolygonA );
+}
+
+sal_uInt16 PolyPolygon::Count() const
+{
+ return mpImplPolyPolygon->mvPolyAry.size();
+}
+
+void PolyPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ // Required for DrawEngine
+ if( nHorzMove || nVertMove )
+ {
+ // move points
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].Move( nHorzMove, nVertMove );
+ }
+}
+
+void PolyPolygon::Translate( const Point& rTrans )
+{
+ // move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Translate( rTrans );
+}
+
+void PolyPolygon::Scale( double fScaleX, double fScaleY )
+{
+ // Move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Scale( fScaleX, fScaleY );
+}
+
+void PolyPolygon::Rotate( const Point& rCenter, Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+
+ if( nAngle10 )
+ {
+ const double fAngle = toRadians(nAngle10);
+ Rotate( rCenter, sin( fAngle ), cos( fAngle ) );
+ }
+}
+
+void PolyPolygon::Rotate( const Point& rCenter, double fSin, double fCos )
+{
+ // move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Rotate( rCenter, fSin, fCos );
+}
+
+void PolyPolygon::Clip( const tools::Rectangle& rRect )
+{
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ sal_uInt16 i;
+
+ if ( !nPolyCount )
+ return;
+
+ // If there are bezier curves involved, Polygon::Clip() is broken.
+ // Use a temporary B2DPolyPolygon for the clipping.
+ for ( i = 0; i < nPolyCount; i++ )
+ {
+ if(mpImplPolyPolygon->mvPolyAry[i].HasFlags())
+ {
+ const basegfx::B2DPolyPolygon aPoly(
+ basegfx::utils::clipPolyPolygonOnRange(
+ getB2DPolyPolygon(),
+ basegfx::B2DRange(
+ rRect.Left(),
+ rRect.Top(),
+ rRect.Right() + 1,
+ rRect.Bottom() + 1),
+ true,
+ false));
+ *this = PolyPolygon( aPoly );
+ return;
+ }
+ }
+
+ // Clip every polygon, deleting the empty ones
+ for ( i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].Clip( rRect );
+ while ( nPolyCount )
+ {
+ if ( GetObject( nPolyCount-1 ).GetSize() <= 2 )
+ Remove( nPolyCount-1 );
+ nPolyCount--;
+ }
+}
+
+tools::Rectangle PolyPolygon::GetBoundRect() const
+{
+ tools::Long nXMin=0, nXMax=0, nYMin=0, nYMax=0;
+ bool bFirst = true;
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+
+ for ( sal_uInt16 n = 0; n < nPolyCount; n++ )
+ {
+ const tools::Polygon* pPoly = &mpImplPolyPolygon->mvPolyAry[n];
+ const Point* pAry = pPoly->GetConstPointAry();
+ sal_uInt16 nPointCount = pPoly->GetSize();
+
+ for ( sal_uInt16 i = 0; i < nPointCount; i++ )
+ {
+ const Point* pPt = &pAry[ i ];
+
+ if ( bFirst )
+ {
+ nXMin = nXMax = pPt->X();
+ nYMin = nYMax = pPt->Y();
+ bFirst = false;
+ }
+ else
+ {
+ if ( pPt->X() < nXMin )
+ nXMin = pPt->X();
+ if ( pPt->X() > nXMax )
+ nXMax = pPt->X();
+ if ( pPt->Y() < nYMin )
+ nYMin = pPt->Y();
+ if ( pPt->Y() > nYMax )
+ nYMax = pPt->Y();
+ }
+ }
+ }
+
+ if ( !bFirst )
+ return tools::Rectangle( nXMin, nYMin, nXMax, nYMax );
+ else
+ return tools::Rectangle();
+}
+
+Polygon& PolyPolygon::operator[]( sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::[](): nPos >= nSize");
+
+ return mpImplPolyPolygon->mvPolyAry[nPos];
+}
+
+PolyPolygon& PolyPolygon::operator=( const tools::PolyPolygon& rPolyPoly )
+{
+ mpImplPolyPolygon = rPolyPoly.mpImplPolyPolygon;
+ return *this;
+}
+
+PolyPolygon& PolyPolygon::operator=( tools::PolyPolygon&& rPolyPoly ) noexcept
+{
+ mpImplPolyPolygon = std::move(rPolyPoly.mpImplPolyPolygon);
+ return *this;
+}
+
+bool PolyPolygon::operator==( const tools::PolyPolygon& rPolyPoly ) const
+{
+ return rPolyPoly.mpImplPolyPolygon == mpImplPolyPolygon;
+}
+
+SvStream& ReadPolyPolygon( SvStream& rIStream, tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 nPolyCount(0);
+
+ // Read number of polygons
+ rIStream.ReadUInt16( nPolyCount );
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
+ if (nPolyCount > nMaxRecords)
+ {
+ SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolyCount << " claimed, truncating");
+ nPolyCount = nMaxRecords;
+ }
+
+ if( nPolyCount )
+ {
+ rPolyPoly.mpImplPolyPolygon->mvPolyAry.resize(nPolyCount);
+
+ tools::Polygon aTempPoly;
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ {
+ ReadPolygon( rIStream, aTempPoly );
+ rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] = aTempPoly;
+ }
+ }
+ else
+ rPolyPoly = tools::PolyPolygon();
+
+ return rIStream;
+}
+
+SvStream& WritePolyPolygon( SvStream& rOStream, const tools::PolyPolygon& rPolyPoly )
+{
+ // Write number of polygons
+ sal_uInt16 nPolyCount = rPolyPoly.mpImplPolyPolygon->mvPolyAry.size();
+ rOStream.WriteUInt16( nPolyCount );
+
+ // output polygons
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ WritePolygon( rOStream, rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] );
+
+ return rOStream;
+}
+
+void PolyPolygon::Read( SvStream& rIStream )
+{
+ VersionCompatRead aCompat(rIStream);
+
+ sal_uInt16 nPolyCount(0);
+
+ // Read number of polygons
+ rIStream.ReadUInt16( nPolyCount );
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
+ if (nPolyCount > nMaxRecords)
+ {
+ SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolyCount << " claimed, truncating");
+ nPolyCount = nMaxRecords;
+ }
+
+ if( nPolyCount )
+ {
+ mpImplPolyPolygon->mvPolyAry.clear();
+
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ {
+ tools::Polygon aTempPoly;
+ aTempPoly.ImplRead( rIStream );
+ mpImplPolyPolygon->mvPolyAry.emplace_back( aTempPoly );
+ }
+ }
+ else
+ *this = tools::PolyPolygon();
+}
+
+void PolyPolygon::Write( SvStream& rOStream ) const
+{
+ VersionCompatWrite aCompat(rOStream, 1);
+
+ // Write number of polygons
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ rOStream.WriteUInt16( nPolyCount );
+
+ // Output polygons
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].ImplWrite( rOStream );
+}
+
+// convert to basegfx::B2DPolyPolygon and return
+basegfx::B2DPolyPolygon PolyPolygon::getB2DPolyPolygon() const
+{
+ basegfx::B2DPolyPolygon aRetval;
+
+ for(size_t a(0); a < mpImplPolyPolygon->mvPolyAry.size(); a++)
+ {
+ tools::Polygon const & rCandidate = mpImplPolyPolygon->mvPolyAry[a];
+ aRetval.append(rCandidate.getB2DPolygon());
+ }
+
+ return aRetval;
+}
+
+// constructor to convert from basegfx::B2DPolyPolygon
+PolyPolygon::PolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
+ : mpImplPolyPolygon(rPolyPolygon)
+{
+}
+
+} /* namespace tools */
+
+ImplPolyPolygon::ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ const sal_uInt16 nCount(sal_uInt16(rPolyPolygon.count()));
+ DBG_ASSERT(sal_uInt32(nCount) == rPolyPolygon.count(),
+ "PolyPolygon::PolyPolygon: Too many sub-polygons in given basegfx::B2DPolyPolygon (!)");
+
+ if ( nCount )
+ {
+ mvPolyAry.resize( nCount );
+
+ for(sal_uInt16 a(0); a < nCount; a++)
+ {
+ const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(sal_uInt32(a)));
+ mvPolyAry[a] = tools::Polygon( aCandidate );
+ }
+ }
+ else
+ mvPolyAry.reserve(16);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/svborder.cxx b/tools/source/generic/svborder.cxx
new file mode 100644
index 0000000000..a61c79b0e4
--- /dev/null
+++ b/tools/source/generic/svborder.cxx
@@ -0,0 +1,35 @@
+/* -*- 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 <tools/svborder.hxx>
+#include <tools/gen.hxx>
+
+tools::Rectangle& operator+=(tools::Rectangle& rRect, const SvBorder& rBorder)
+{
+ // call GetSize first due to Empty-Rect
+ Size aS(rRect.GetSize());
+ aS.AdjustWidth(rBorder.Left() + rBorder.Right());
+ aS.AdjustHeight(rBorder.Top() + rBorder.Bottom());
+
+ rRect.AdjustLeft(-(rBorder.Left()));
+ rRect.AdjustTop(-(rBorder.Top()));
+ rRect.SetSize(aS);
+ return rRect;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */