blob: ef5b47543ab8450ada224f09ecd6666ffaf9cc31 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_basegfx.hxx"
#include <cstdio>
#include <osl/diagnose.h>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <com/sun/star/drawing/LineCap.hpp>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
//////////////////////////////////////////////////////////////////////////////
namespace basegfx
{
namespace tools
{
B2DPolyPolygon createAreaGeometryForLineStartEnd(
const B2DPolygon& rCandidate,
const B2DPolyPolygon& rArrow,
bool bStart,
double fWidth,
double fCandidateLength,
double fDockingPosition, // 0->top, 1->bottom
double* pConsumedLength)
{
B2DPolyPolygon aRetval;
OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0,
"createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
if(fWidth < 0.0)
{
fWidth = -fWidth;
}
if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
{
if(fDockingPosition < 0.0)
{
fDockingPosition = 0.0;
}
else if(fDockingPosition > 1.0)
{
fDockingPosition = 1.0;
}
// init return value from arrow
aRetval.append(rArrow);
// get size of the arrow
const B2DRange aArrowSize(getRange(rArrow));
// build ArrowTransform; center in X, align with axis in Y
B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
-aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
// scale to target size
const double fArrowScale(fWidth / (aArrowSize.getRange().getX()));
aArrowTransform.scale(fArrowScale, fArrowScale);
// get arrow size in Y
B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
aUpperCenter *= aArrowTransform;
const double fArrowYLength(B2DVector(aUpperCenter).getLength());
// move arrow to have docking position centered
aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition);
// prepare polygon length
if(fTools::equalZero(fCandidateLength))
{
fCandidateLength = getLength(rCandidate);
}
// get the polygon vector we want to plant this arrow on
const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition));
const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
const B2DVector aTail(getPositionAbsolute(rCandidate,
(bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
// from that vector, take the needed rotation and add rotate for arrow to transformation
const B2DVector aTargetDirection(aHead - aTail);
const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));
// rotate around docking position
aArrowTransform.rotate(fRotation);
// move arrow docking position to polygon head
aArrowTransform.translate(aHead.getX(), aHead.getY());
// transform retval and close
aRetval.transform(aArrowTransform);
aRetval.setClosed(true);
// if pConsumedLength is asked for, fill it
if(pConsumedLength)
{
*pConsumedLength = fConsumedLength;
}
}
return aRetval;
}
} // end of namespace tools
} // end of namespace basegfx
//////////////////////////////////////////////////////////////////////////////
namespace basegfx
{
// anonymus namespace for local helpers
namespace
{
bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
{
// isBezier() is true, already tested by caller
const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
if(aEdge.equalZero())
{
// start and end point the same, but control vectors used -> baloon curve loop
// is not a simple edge
return false;
}
// get tangentA and scalar with edge
const B2DVector aTangentA(rCandidate.getTangent(0.0));
const double fScalarAE(aEdge.scalar(aTangentA));
if(fTools::lessOrEqual(fScalarAE, 0.0))
{
// angle between TangentA and Edge is bigger or equal 90 degrees
return false;
}
// get self-scalars for E and A
const double fScalarE(aEdge.scalar(aEdge));
const double fScalarA(aTangentA.scalar(aTangentA));
const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
{
// length of TangentA is more than fMaxPartOfEdge of length of edge
return false;
}
if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
{
// angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
return false;
}
// get tangentB and scalar with edge
const B2DVector aTangentB(rCandidate.getTangent(1.0));
const double fScalarBE(aEdge.scalar(aTangentB));
if(fTools::lessOrEqual(fScalarBE, 0.0))
{
// angle between TangentB and Edge is bigger or equal 90 degrees
return false;
}
// get self-scalar for B
const double fScalarB(aTangentB.scalar(aTangentB));
if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
{
// length of TangentB is more than fMaxPartOfEdge of length of edge
return false;
}
if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
{
// angle between TangentB and Edge is bigger or equal defined by fMaxCos
return false;
}
return true;
}
void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
{
if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
{
rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
}
else
{
B2DCubicBezier aLeft, aRight;
rCandidate.split(0.5, &aLeft, &aRight);
impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
}
}
B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
{
const sal_uInt32 nPointCount(rCandidate.count());
if(rCandidate.areControlPointsUsed() && nPointCount)
{
const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
B2DPolygon aRetval;
B2DCubicBezier aEdge;
// prepare edge for loop
aEdge.setStartPoint(rCandidate.getB2DPoint(0));
aRetval.append(aEdge.getStartPoint());
for(sal_uInt32 a(0); a < nEdgeCount; a++)
{
// fill B2DCubicBezier
const sal_uInt32 nNextIndex((a + 1) % nPointCount);
aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
// get rid of unnecessary bezier segments
aEdge.testAndSolveTrivialBezier();
if(aEdge.isBezier())
{
// before splitting recursively with internal simple criteria, use
// ExtremumPosFinder to remove those
::std::vector< double > aExtremumPositions;
aExtremumPositions.reserve(4);
aEdge.getAllExtremumPositions(aExtremumPositions);
const sal_uInt32 nCount(aExtremumPositions.size());
if(nCount)
{
if(nCount > 1)
{
// create order from left to right
::std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
}
for(sal_uInt32 b(0); b < nCount;)
{
// split aEdge at next split pos
B2DCubicBezier aLeft;
const double fSplitPos(aExtremumPositions[b++]);
aEdge.split(fSplitPos, &aLeft, &aEdge);
aLeft.testAndSolveTrivialBezier();
// consume left part
if(aLeft.isBezier())
{
impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
}
else
{
aRetval.append(aLeft.getEndPoint());
}
if(b < nCount)
{
// correct the remaining split positions to fit to shortened aEdge
const double fScaleFactor(1.0 / (1.0 - fSplitPos));
for(sal_uInt32 c(b); c < nCount; c++)
{
aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
}
}
}
// test the shortened rest of aEdge
aEdge.testAndSolveTrivialBezier();
// consume right part
if(aEdge.isBezier())
{
impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
}
else
{
aRetval.append(aEdge.getEndPoint());
}
}
else
{
impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
}
}
else
{
// straight edge, add point
aRetval.append(aEdge.getEndPoint());
}
// prepare edge for next step
aEdge.setStartPoint(aEdge.getEndPoint());
}
// copy closed flag and check for double points
aRetval.setClosed(rCandidate.isClosed());
aRetval.removeDoublePoints();
return aRetval;
}
else
{
return rCandidate;
}
}
B2DPolygon createAreaGeometryForEdge(
const B2DCubicBezier& rEdge,
double fHalfLineWidth,
bool bStartRound,
bool bEndRound,
bool bStartSquare,
bool bEndSquare)
{
// create polygon for edge
// Unfortunately, while it would be geometrically correct to not add
// the in-between points EdgeEnd and EdgeStart, it leads to rounding
// errors when converting to integer polygon coordinates for painting
if(rEdge.isBezier())
{
// prepare target and data common for upper and lower
B2DPolygon aBezierPolygon;
const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
const double fEdgeLength(aPureEdgeVector.getLength());
const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
// create upper displacement vectors and check if they cut
const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
double fCutA(0.0);
const tools::CutFlagValue aCutA(tools::findCut(
rEdge.getStartPoint(), aPerpendStartA,
rEdge.getEndPoint(), aPerpendEndA,
CUTFLAG_ALL, &fCutA));
const bool bCutA(CUTFLAG_NONE != aCutA);
// create lower displacement vectors and check if they cut
const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
double fCutB(0.0);
const tools::CutFlagValue aCutB(tools::findCut(
rEdge.getEndPoint(), aPerpendEndB,
rEdge.getStartPoint(), aPerpendStartB,
CUTFLAG_ALL, &fCutB));
const bool bCutB(CUTFLAG_NONE != aCutB);
// check if cut happens
const bool bCut(bCutA || bCutB);
B2DPoint aCutPoint;
// create left edge
if(bStartRound || bStartSquare)
{
if(bStartRound)
{
basegfx::B2DPolygon aStartPolygon(tools::createHalfUnitCircle());
aStartPolygon.transform(
tools::createScaleShearXRotateTranslateB2DHomMatrix(
fHalfLineWidth, fHalfLineWidth,
0.0,
atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2,
rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
aBezierPolygon.append(aStartPolygon);
}
else // bStartSquare
{
const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
if(bCutB)
{
aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
}
aBezierPolygon.append(aStart + aPerpendStartB);
aBezierPolygon.append(aStart + aPerpendStartA);
if(bCutA)
{
aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
}
}
}
else
{
// append original in-between point
aBezierPolygon.append(rEdge.getStartPoint());
}
// create upper edge.
{
if(bCutA)
{
// calculate cut point and add
aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
aBezierPolygon.append(aCutPoint);
}
else
{
// create scaled bezier segment
const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
const B2DVector aEdge(aEnd - aStart);
const double fLength(aEdge.getLength());
const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
aBezierPolygon.append(aStart);
aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
}
}
// create right edge
if(bEndRound || bEndSquare)
{
if(bEndRound)
{
basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());
aEndPolygon.transform(
tools::createScaleShearXRotateTranslateB2DHomMatrix(
fHalfLineWidth, fHalfLineWidth,
0.0,
atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2,
rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
aBezierPolygon.append(aEndPolygon);
}
else // bEndSquare
{
const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
if(bCutA)
{
aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
}
aBezierPolygon.append(aEnd + aPerpendEndA);
aBezierPolygon.append(aEnd + aPerpendEndB);
if(bCutB)
{
aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
}
}
}
else
{
// append original in-between point
aBezierPolygon.append(rEdge.getEndPoint());
}
// create lower edge.
{
if(bCutB)
{
// calculate cut point and add
aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
aBezierPolygon.append(aCutPoint);
}
else
{
// create scaled bezier segment
const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
const B2DVector aEdge(aEnd - aStart);
const double fLength(aEdge.getLength());
const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
aBezierPolygon.append(aStart);
aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
}
}
// close
aBezierPolygon.setClosed(true);
if(bStartRound || bEndRound)
{
// double points possible when round caps are used at start or end
aBezierPolygon.removeDoublePoints();
}
if(bCut && ((bStartRound || bStartSquare) && (bEndRound || bEndSquare)))
{
// When cut exists and both ends are extended with caps, a self-intersecting polygon
// is created; one cut point is known, but there is a 2nd one in the caps geometry.
// Solve by using tooling.
// Remark: This nearly never happens due to curve preparations to extreme points
// and maximum angle turning, but I constructed a test case and checkd that it is
// working propery.
const B2DPolyPolygon aTemp(tools::solveCrossovers(aBezierPolygon));
const sal_uInt32 nTempCount(aTemp.count());
if(nTempCount)
{
if(nTempCount > 1)
{
// as expected, multiple polygons (with same orientation). Remove
// the one which contains aCutPoint, or better take the one without
for (sal_uInt32 a(0); a < nTempCount; a++)
{
aBezierPolygon = aTemp.getB2DPolygon(a);
const sal_uInt32 nCandCount(aBezierPolygon.count());
for(sal_uInt32 b(0); b < nCandCount; b++)
{
if(aCutPoint.equal(aBezierPolygon.getB2DPoint(b)))
{
aBezierPolygon.clear();
break;
}
}
if(aBezierPolygon.count())
{
break;
}
}
OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
}
else
{
// none found, use result
aBezierPolygon = aTemp.getB2DPolygon(0);
}
}
else
{
OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
}
}
// return
return aBezierPolygon;
}
else
{
// Get start and end point, create tangent and set to needed length
B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
aTangent.setLength(fHalfLineWidth);
// prepare return value
B2DPolygon aEdgePolygon;
// buffered angle
double fAngle(0.0);
bool bAngle(false);
// buffered perpendicular
B2DVector aPerpend;
bool bPerpend(false);
// create left vertical
if(bStartRound)
{
aEdgePolygon = tools::createHalfUnitCircle();
fAngle = atan2(aTangent.getY(), aTangent.getX());
bAngle = true;
aEdgePolygon.transform(
tools::createScaleShearXRotateTranslateB2DHomMatrix(
fHalfLineWidth, fHalfLineWidth,
0.0,
fAngle + F_PI2,
rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
}
else
{
aPerpend.setX(-aTangent.getY());
aPerpend.setY(aTangent.getX());
bPerpend = true;
if(bStartSquare)
{
const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
aEdgePolygon.append(aStart + aPerpend);
aEdgePolygon.append(aStart - aPerpend);
}
else
{
aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
}
}
// create right vertical
if(bEndRound)
{
basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());
if(!bAngle)
{
fAngle = atan2(aTangent.getY(), aTangent.getX());
}
aEndPolygon.transform(
tools::createScaleShearXRotateTranslateB2DHomMatrix(
fHalfLineWidth, fHalfLineWidth,
0.0,
fAngle - F_PI2,
rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
aEdgePolygon.append(aEndPolygon);
}
else
{
if(!bPerpend)
{
aPerpend.setX(-aTangent.getY());
aPerpend.setY(aTangent.getX());
}
if(bEndSquare)
{
const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
aEdgePolygon.append(aEnd - aPerpend);
aEdgePolygon.append(aEnd + aPerpend);
}
else
{
aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
}
}
// close and return
aEdgePolygon.setClosed(true);
return aEdgePolygon;
}
}
B2DPolygon createAreaGeometryForJoin(
const B2DVector& rTangentPrev,
const B2DVector& rTangentEdge,
const B2DVector& rPerpendPrev,
const B2DVector& rPerpendEdge,
const B2DPoint& rPoint,
double fHalfLineWidth,
B2DLineJoin eJoin,
double fMiterMinimumAngle)
{
OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
// LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
B2DPolygon aEdgePolygon;
const B2DPoint aStartPoint(rPoint + rPerpendPrev);
const B2DPoint aEndPoint(rPoint + rPerpendEdge);
// test if for Miter, the angle is too small and the fallback
// to bevel needs to be used
if(B2DLINEJOIN_MITER == eJoin)
{
const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
if((F_PI - fAngle) < fMiterMinimumAngle)
{
// fallback to bevel
eJoin = B2DLINEJOIN_BEVEL;
}
}
switch(eJoin)
{
case B2DLINEJOIN_MITER :
{
aEdgePolygon.append(aEndPoint);
aEdgePolygon.append(rPoint);
aEdgePolygon.append(aStartPoint);
// Look for the cut point between start point along rTangentPrev and
// end point along rTangentEdge. -rTangentEdge should be used, but since
// the cut value is used for interpolating along the first edge, the negation
// is not needed since the same fCut will be found on the first edge.
// If it exists, insert it to complete the mitered fill polygon.
double fCutPos(0.0);
tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);
if(0.0 != fCutPos)
{
const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
aEdgePolygon.append(aCutPoint);
}
break;
}
case B2DLINEJOIN_ROUND :
{
// use tooling to add needed EllipseSegment
double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
// atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
if(fAngleStart < 0.0)
{
fAngleStart += F_2PI;
}
if(fAngleEnd < 0.0)
{
fAngleEnd += F_2PI;
}
const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
if(aBow.count() > 1)
{
// #i101491#
// use the original start/end positions; the ones from bow creation may be numerically
// different due to their different creation. To guarantee good merging quality with edges
// and edge roundings (and to reduce point count)
aEdgePolygon = aBow;
aEdgePolygon.setB2DPoint(0, aStartPoint);
aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
aEdgePolygon.append(rPoint);
break;
}
else
{
// wanted fall-through to default
}
}
default: // B2DLINEJOIN_BEVEL
{
aEdgePolygon.append(aEndPoint);
aEdgePolygon.append(rPoint);
aEdgePolygon.append(aStartPoint);
break;
}
}
// create last polygon part for edge
aEdgePolygon.setClosed(true);
return aEdgePolygon;
}
} // end of anonymus namespace
namespace tools
{
B2DPolyPolygon createAreaGeometry(
const B2DPolygon& rCandidate,
double fHalfLineWidth,
B2DLineJoin eJoin,
com::sun::star::drawing::LineCap eCap,
double fMaxAllowedAngle,
double fMaxPartOfEdge,
double fMiterMinimumAngle)
{
if(fMaxAllowedAngle > F_PI2)
{
fMaxAllowedAngle = F_PI2;
}
else if(fMaxAllowedAngle < 0.01 * F_PI2)
{
fMaxAllowedAngle = 0.01 * F_PI2;
}
if(fMaxPartOfEdge > 1.0)
{
fMaxPartOfEdge = 1.0;
}
else if(fMaxPartOfEdge < 0.01)
{
fMaxPartOfEdge = 0.01;
}
if(fMiterMinimumAngle > F_PI)
{
fMiterMinimumAngle = F_PI;
}
else if(fMiterMinimumAngle < 0.01 * F_PI)
{
fMiterMinimumAngle = 0.01 * F_PI;
}
B2DPolygon aCandidate(rCandidate);
const double fMaxCos(cos(fMaxAllowedAngle));
aCandidate.removeDoublePoints();
aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
const sal_uInt32 nPointCount(aCandidate.count());
if(nPointCount)
{
B2DPolyPolygon aRetval;
const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
const bool bIsClosed(aCandidate.isClosed());
const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
const bool bLineCap(!bIsClosed && com::sun::star::drawing::LineCap_BUTT != eCap);
if(nEdgeCount)
{
B2DCubicBezier aEdge;
B2DCubicBezier aPrev;
// prepare edge
aEdge.setStartPoint(aCandidate.getB2DPoint(0));
if(bIsClosed && bEventuallyCreateLineJoin)
{
// prepare previous edge
const sal_uInt32 nPrevIndex(nPointCount - 1);
aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
aPrev.setEndPoint(aEdge.getStartPoint());
}
for(sal_uInt32 a(0); a < nEdgeCount; a++)
{
// fill current Edge
const sal_uInt32 nNextIndex((a + 1) % nPointCount);
aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
// check and create linejoin
if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
{
B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
if(ORIENTATION_NEUTRAL == aOrientation)
{
// they are parallell or empty; if they are both not zero and point
// in opposite direction, a half-circle is needed
if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
{
const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
if(fTools::equal(fAngle, F_PI))
{
// for half-circle production, fallback to positive
// orientation
aOrientation = ORIENTATION_POSITIVE;
}
}
}
if(ORIENTATION_POSITIVE == aOrientation)
{
const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
aRetval.append(
createAreaGeometryForJoin(
aTangentPrev,
aTangentEdge,
aPerpendPrev,
aPerpendEdge,
aEdge.getStartPoint(),
fHalfLineWidth,
eJoin,
fMiterMinimumAngle));
}
else if(ORIENTATION_NEGATIVE == aOrientation)
{
const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
aRetval.append(
createAreaGeometryForJoin(
aTangentEdge,
aTangentPrev,
aPerpendEdge,
aPerpendPrev,
aEdge.getStartPoint(),
fHalfLineWidth,
eJoin,
fMiterMinimumAngle));
}
}
// create geometry for edge
const bool bLast(a + 1 == nEdgeCount);
if(bLineCap)
{
const bool bFirst(!a);
aRetval.append(
createAreaGeometryForEdge(
aEdge,
fHalfLineWidth,
bFirst && com::sun::star::drawing::LineCap_ROUND == eCap,
bLast && com::sun::star::drawing::LineCap_ROUND == eCap,
bFirst && com::sun::star::drawing::LineCap_SQUARE == eCap,
bLast && com::sun::star::drawing::LineCap_SQUARE == eCap));
}
else
{
aRetval.append(
createAreaGeometryForEdge(
aEdge,
fHalfLineWidth,
false,
false,
false,
false));
}
// prepare next step
if(!bLast)
{
if(bEventuallyCreateLineJoin)
{
aPrev = aEdge;
}
aEdge.setStartPoint(aEdge.getEndPoint());
}
}
}
return aRetval;
}
else
{
return B2DPolyPolygon(rCandidate);
}
}
} // end of namespace tools
} // end of namespace basegfx
//////////////////////////////////////////////////////////////////////////////
// eof