diff options
Diffstat (limited to 'tools/source/generic')
-rw-r--r-- | tools/source/generic/b3dtrans.cxx | 456 | ||||
-rw-r--r-- | tools/source/generic/bigint.cxx | 852 | ||||
-rw-r--r-- | tools/source/generic/color.cxx | 266 | ||||
-rw-r--r-- | tools/source/generic/config.cxx | 944 | ||||
-rw-r--r-- | tools/source/generic/fract.cxx | 537 | ||||
-rw-r--r-- | tools/source/generic/gen.cxx | 225 | ||||
-rw-r--r-- | tools/source/generic/line.cxx | 140 | ||||
-rw-r--r-- | tools/source/generic/point.cxx | 86 | ||||
-rw-r--r-- | tools/source/generic/poly.cxx | 1866 | ||||
-rw-r--r-- | tools/source/generic/poly2.cxx | 520 | ||||
-rw-r--r-- | tools/source/generic/svborder.cxx | 35 |
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: */ |