diff options
author | Vladimir Glazounov <vg@openoffice.org> | 2008-08-19 23:01:47 +0000 |
---|---|---|
committer | Vladimir Glazounov <vg@openoffice.org> | 2008-08-19 23:01:47 +0000 |
commit | ba2d94d15a8ade2408bcde1c0b20307cd2efde4b (patch) | |
tree | 4aa1a0d9d66d400e17aeee55fdd52f67131edef6 /basegfx | |
parent | ca68af5c340c5c4d3d2f92f33009365b89b0a86f (diff) |
INTEGRATION: CWS aw033 (1.5.6); FILE MERGED
2008/05/14 14:40:29 aw 1.5.6.11: RESYNC: (1.5-1.6); FILE MERGED
2008/05/14 09:16:32 aw 1.5.6.10: #i39532# aw033 progresses from git
2007/11/23 09:43:05 aw 1.5.6.9: #i39532# warning corrections
2007/11/22 14:56:58 aw 1.5.6.8: #i39532# polygon bezier changes
2007/11/19 10:17:02 aw 1.5.6.7: #i39532# Lot of changes to make polygon stuff bezier-able
2007/11/07 14:24:29 aw 1.5.6.6: #i39532# committing to have a base for HDU
2007/10/18 09:27:00 hdu 1.5.6.5: #i75669# fix typo
2007/10/18 09:23:10 hdu 1.5.6.4: #i75669# resegment polygon with curves before line->area conversion
2007/10/17 12:22:53 hdu 1.5.6.3: #i75669# fix createAreaGeometryForSimplePolygon() after resync changes
2007/08/13 15:28:43 aw 1.5.6.2: #i39532# changes after resync
2007/04/26 14:30:26 hdu 1.5.6.1: #i75669# added method createAreaGeometryForSimplePolygon()
Diffstat (limited to 'basegfx')
-rw-r--r-- | basegfx/source/polygon/b2dlinegeometry.cxx | 846 |
1 files changed, 551 insertions, 295 deletions
diff --git a/basegfx/source/polygon/b2dlinegeometry.cxx b/basegfx/source/polygon/b2dlinegeometry.cxx index d34d7acce99c..0cda1036e13b 100644 --- a/basegfx/source/polygon/b2dlinegeometry.cxx +++ b/basegfx/source/polygon/b2dlinegeometry.cxx @@ -7,7 +7,7 @@ * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: b2dlinegeometry.cxx,v $ - * $Revision: 1.6 $ + * $Revision: 1.7 $ * * This file is part of OpenOffice.org. * @@ -30,6 +30,7 @@ // 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> @@ -38,6 +39,107 @@ #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/range/b2drange.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dcubicbezier.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 + B2DHomMatrix aArrowTransform; + + // center in X, align with axis in Y + aArrowTransform.translate(-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 ////////////////////////////////////////////////////////////////////////////// @@ -46,392 +148,546 @@ namespace basegfx // anonymus namespace for local helpers namespace { - // create area geometry for given edge. Edge is defined with the - // points rEdgeStart and rEdgeEnd. fHalfLineWidth defines the relative width. - // The created polygon will be positively oriented and free of - // self intersections. - // bCreateInBetweenPoints defines if EdgeStart and EdgeEnd themselves will - // be added (see comment in implementation). - B2DPolygon createAreaGeometryForEdge( - const B2DPoint& rEdgeStart, - const B2DPoint& rEdgeEnd, - double fHalfLineWidth, - bool bCreateInBetweenPoints) + bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) { - OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForEdge: LineWidth too small (!)"); - B2DPolygon aRetval; + // isBezier() is true, already tested by caller + const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint()); - // get perpendicular vector for reaching the outer edges - const B2DVector aEdgeVector(rEdgeEnd - rEdgeStart); - B2DVector aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeVector)); - aPerpendEdgeVector *= fHalfLineWidth; + if(aEdge.equalZero()) + { + // start and end point the same, but control vectors used -> baloon curve loop + // is not a simple edge + return false; + } - // create polygon for edge - // Unfortunately, while it would be geometrically correct to not add - // the in-between points rEdgeEnd and rEdgeStart, it leads to rounding - // errors when converting to integer polygon coordiates for painting. - aRetval.append(rEdgeStart - aPerpendEdgeVector); - aRetval.append(rEdgeEnd - aPerpendEdgeVector); + // get tangentA and scalar with edge + const B2DVector aTangentA(rCandidate.getTangent(0.0)); + const double fScalarAE(aEdge.scalar(aTangentA)); - if(bCreateInBetweenPoints) + if(fTools::lessOrEqual(fScalarAE, 0.0)) { - aRetval.append(rEdgeEnd); + // angle between TangentA and Edge is bigger or equal 90 degrees + return false; } - aRetval.append(rEdgeEnd + aPerpendEdgeVector); - aRetval.append(rEdgeStart + aPerpendEdgeVector); + // 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(bCreateInBetweenPoints) + if(fTools::moreOrEqual(fScalarA, fLengthCompareE)) { - aRetval.append(rEdgeStart); + // length of TangentA is more than fMaxPartOfEdge of length of edge + return false; } - aRetval.setClosed(true); + 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)); -#ifdef DBG_UTIL - // check orientation (debug only) - if(tools::getOrientation(aRetval) == ORIENTATION_NEGATIVE) + if(fTools::lessOrEqual(fScalarBE, 0.0)) { - OSL_ENSURE(false, "createAreaGeometryForEdge: orientation of return value is negative (!)"); + // angle between TangentB and Edge is bigger or equal 90 degrees + return false; } -#endif - return aRetval; + // 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; } - // create join polygon for given angle. Angle is defined with the - // points rLeft, rCenter and rRight. The given join type defines how - // the join segment will be created. fHalfLineWidth defines the relative width. - // fDegreeStepWidth is used when rounding edges. - // fMiterMinimumAngle is used to define when miter is forced to bevel. - // The created polygon will be positively or neuteral oriented and free of - // self intersections. - B2DPolygon createAreaGeometryForJoin( - const B2DPoint& rLeft, - const B2DPoint& rCenter, - const B2DPoint& rRight, - double fHalfLineWidth, - tools::B2DLineJoin eJoin, - double fDegreeStepWidth, - double /*fMiterMinimumAngle*/) + void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth) { - OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)"); - OSL_ENSURE(fDegreeStepWidth > 0.0, "createAreaGeometryForJoin: DegreeStepWidth too small (!)"); - OSL_ENSURE(tools::B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)"); - B2DPolygon aRetval; + if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad)) + { + rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint()); + } + else + { + B2DCubicBezier aLeft, aRight; + rCandidate.split(0.5, &aLeft, &aRight); - // get perpendicular vector for left and right - const B2DVector aLeftVector(rCenter - rLeft); - B2DVector aPerpendLeftVector(getNormalizedPerpendicular(aLeftVector)); - const B2DVector aRightVector(rRight - rCenter); - B2DVector aPerpendRightVector(getNormalizedPerpendicular(aRightVector)); + impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); + impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); + } + } - // get vector orientation - B2VectorOrientation aOrientation(getOrientation(aPerpendLeftVector, aPerpendRightVector)); + B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) + { + const sal_uInt32 nPointCount(rCandidate.count()); - if(ORIENTATION_NEUTRAL != aOrientation) + if(rCandidate.areControlPointsUsed() && nPointCount) { - // prepare perpend vectors to be able to go from left to right. - // also multiply with fHalfLineWidth to get geometric vectors with correct length - if(ORIENTATION_POSITIVE == aOrientation) - { - // mirror to have them above the edge vectors - const double fNegativeHalfLineWidth(-fHalfLineWidth); - aPerpendLeftVector *= fNegativeHalfLineWidth; - aPerpendRightVector *= fNegativeHalfLineWidth; - } - else - { - // exchange left and right - const B2DVector aTemp(aPerpendLeftVector.getX() * fHalfLineWidth, aPerpendLeftVector.getY() * fHalfLineWidth); - aPerpendLeftVector.setX(aPerpendRightVector.getX() * fHalfLineWidth); - aPerpendLeftVector.setY(aPerpendRightVector.getY() * fHalfLineWidth); - aPerpendRightVector = aTemp; - } + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DPolygon aRetval; + B2DCubicBezier aEdge; - // test if for Miter, the angle is too small - if(tools::B2DLINEJOIN_MITER == eJoin) + // prepare edge for loop + aEdge.setStartPoint(rCandidate.getB2DPoint(0)); + aRetval.append(aEdge.getStartPoint()); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) { - const double fAngle(fabs(aPerpendLeftVector.angle(aPerpendRightVector))); + // fill B2DCubicBezier + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aEdge.setControlPointA(rCandidate.getNextControlPoint(a)); + aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); - if((F_PI - fAngle) < (15.0 * F_PI180)) - { - // force to bevel - eJoin = tools::B2DLINEJOIN_BEVEL; - } - } + // get rid of unnecessary bezier segments + aEdge.testAndSolveTrivialBezier(); - // create specific edge polygon - switch(eJoin) - { - case tools::B2DLINEJOIN_MIDDLE : - case tools::B2DLINEJOIN_BEVEL : + if(aEdge.isBezier()) { - // create polygon for edge, go from left to right - aRetval.append(rCenter); - aRetval.append(rCenter + aPerpendLeftVector); - aRetval.append(rCenter + aPerpendRightVector); - aRetval.setClosed(true); + // before splitting recursively with internal simple criteria, use + // ExtremumPosFinder to remove those + ::std::vector< double > aExtremumPositions; - break; - } - case tools::B2DLINEJOIN_MITER : - { - // create first polygon part for edge, go from left to right - aRetval.append(rCenter); - aRetval.append(rCenter + aPerpendLeftVector); + aExtremumPositions.reserve(4); + aEdge.getAllExtremumPositions(aExtremumPositions); - double fCutPos(0.0); - const B2DPoint aLeftCutPoint(rCenter + aPerpendLeftVector); - const B2DPoint aRightCutPoint(rCenter + aPerpendRightVector); + const sal_uInt32 nCount(aExtremumPositions.size()); - if(ORIENTATION_POSITIVE == aOrientation) + if(nCount) { - tools::findCut(aLeftCutPoint, aLeftVector, aRightCutPoint, -aRightVector, CUTFLAG_ALL, &fCutPos); - - if(0.0 != fCutPos) + if(nCount > 1) { - const B2DPoint aCutPoint( - interpolate(aLeftCutPoint, aLeftCutPoint + aLeftVector, fCutPos)); - aRetval.append(aCutPoint); + // create order from left to right + ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end()); } - } - else - { - // peroendiculars are exchanged, also use exchanged EdgeVectors - tools::findCut(aLeftCutPoint, -aRightVector, aRightCutPoint, aLeftVector, CUTFLAG_ALL, &fCutPos); - if(0.0 != fCutPos) + for(sal_uInt32 b(0); b < nCount;) { - const B2DPoint aCutPoint( - interpolate(aLeftCutPoint, aLeftCutPoint - aRightVector, fCutPos)); - aRetval.append(aCutPoint); + // 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; + } + } } - } - // create last polygon part for edge - aRetval.append(rCenter + aPerpendRightVector); - aRetval.setClosed(true); + // test the shortened rest of aEdge + aEdge.testAndSolveTrivialBezier(); - break; - } - case tools::B2DLINEJOIN_ROUND : - { - // create first polygon part for edge, go from left to right - aRetval.append(rCenter); - aRetval.append(rCenter + aPerpendLeftVector); - - // get angle and prepare - double fAngle(aPerpendLeftVector.angle(aPerpendRightVector)); - const bool bNegative(fAngle < 0.0); - if(bNegative) + // consume right part + if(aEdge.isBezier()) + { + impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); + } + else + { + aRetval.append(aEdge.getEndPoint()); + } + } + else { - fAngle = fabs(fAngle); + impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); } + } + else + { + // straight edge, add point + aRetval.append(aEdge.getEndPoint()); + } - // substract first step, first position is added to - // the polygon yet - fAngle -= fDegreeStepWidth; + // prepare edge for next step + aEdge.setStartPoint(aEdge.getEndPoint()); + } - // create points as long as angle is > 0.0 - if(fAngle > 0.0) - { - // get start angle - double fAngleOfLeftPerpendVector( - atan2(aPerpendLeftVector.getY(), aPerpendLeftVector.getX())); + // copy closed flag and check for double points + aRetval.setClosed(rCandidate.isClosed()); + aRetval.removeDoublePoints(); - while(fAngle > 0.0) - { - // calculate rotated vector - fAngleOfLeftPerpendVector += (bNegative ? -fDegreeStepWidth : fDegreeStepWidth); - const B2DVector aRotatedVector( - rCenter.getX() + (cos(fAngleOfLeftPerpendVector) * fHalfLineWidth), - rCenter.getY() + (sin(fAngleOfLeftPerpendVector) * fHalfLineWidth)); + return aRetval; + } + else + { + return rCandidate; + } + } - // add point - aRetval.append(aRotatedVector); + B2DPolygon createAreaGeometryForEdge(const B2DCubicBezier& rEdge, double fHalfLineWidth) + { + // 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 + const B2DVector aEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint()); - // substract next step - fAngle -= fDegreeStepWidth; - } - } + if(rEdge.isBezier()) + { + // prepare target and data common for upper and lower + B2DPolygon aBezierPolygon; + const double fEdgeLength(aEdgeVector.getLength()); + const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength)); + const B2DVector aTangentA(rEdge.getTangent(0.0)); + const B2DVector aTangentB(rEdge.getTangent(1.0)); + + // create upper edge. + { + // create displacement vectors and check if they cut + const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * -fHalfLineWidth); + const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * -fHalfLineWidth); + double fCut(0.0); + const tools::CutFlagValue aCut(tools::findCut( + rEdge.getStartPoint(), aPerpendStart, + rEdge.getEndPoint(), aPerpendEnd, + CUTFLAG_ALL, &fCut)); + + if(CUTFLAG_NONE != aCut) + { + // calculate cut point and add + const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStart * fCut)); + aBezierPolygon.append(aCutPoint); + } + else + { + // create scaled bezier segment + const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStart); + const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEnd); + 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 last polygon part for edge - aRetval.append(rCenter + aPerpendRightVector); - aRetval.setClosed(true); + // append original in-between point + aBezierPolygon.append(rEdge.getEndPoint()); - break; + // create lower edge. + { + // create displacement vectors and check if they cut + const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * fHalfLineWidth); + const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * fHalfLineWidth); + double fCut(0.0); + const tools::CutFlagValue aCut(tools::findCut( + rEdge.getEndPoint(), aPerpendEnd, + rEdge.getStartPoint(), aPerpendStart, + CUTFLAG_ALL, &fCut)); + + if(CUTFLAG_NONE != aCut) + { + // calculate cut point and add + const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEnd * fCut)); + aBezierPolygon.append(aCutPoint); + } + else + { + // create scaled bezier segment + const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEnd); + const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStart); + 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); } - case tools::B2DLINEJOIN_NONE: - break; // nothing to add to aRetVal - default: - OSL_ENSURE(false, - "createAreaGeometryForJoin(): unexpected case."); } - } -#ifdef DBG_UTIL - // check orientation (debug only) - if(tools::getOrientation(aRetval) == ORIENTATION_NEGATIVE) - { - OSL_ENSURE(false, "createAreaGeometryForJoin: orientation of return value is negative (!)"); + // append original in-between point + aBezierPolygon.append(rEdge.getStartPoint()); + + // close and return + aBezierPolygon.setClosed(true); + return aBezierPolygon; } -#endif + else + { + const B2DVector aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeVector) * fHalfLineWidth); + B2DPolygon aEdgePolygon; - return aRetval; + // create upper edge + aEdgePolygon.append(rEdge.getStartPoint() - aPerpendEdgeVector); + aEdgePolygon.append(rEdge.getEndPoint() - aPerpendEdgeVector); + + // append original in-between point + aEdgePolygon.append(rEdge.getEndPoint()); + + // create lower edge + aEdgePolygon.append(rEdge.getEndPoint() + aPerpendEdgeVector); + aEdgePolygon.append(rEdge.getStartPoint() + aPerpendEdgeVector); + + // append original in-between point + aEdgePolygon.append(rEdge.getStartPoint()); + + // close and return + aEdgePolygon.setClosed(true); + return aEdgePolygon; + } } - } // end of anonymus namespace - namespace tools - { - B2DPolyPolygon createAreaGeometryForPolygon(const B2DPolygon& rCandidate, + B2DPolygon createAreaGeometryForJoin( + const B2DVector& rTangentPrev, + const B2DVector& rTangentEdge, + const B2DVector& rPerpendPrev, + const B2DVector& rPerpendEdge, + const B2DPoint& rPoint, double fHalfLineWidth, B2DLineJoin eJoin, - double fDegreeStepWidth, double fMiterMinimumAngle) { - OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForPolygon: LineWidth too small (!)"); - OSL_ENSURE(fDegreeStepWidth > 0.0, "createAreaGeometryForPolygon: DegreeStepWidth too small (!)"); - B2DPolyPolygon aRetval; - const sal_uInt32 nCount(rCandidate.count()); + OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)"); + OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)"); - if(rCandidate.isClosed()) + // 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 bool bNeedToCreateJoinPolygon(B2DLINEJOIN_NONE != eJoin); - bool bLastNeededToCreateJoinPolygon(false); + const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge))); - for(sal_uInt32 a(0L); a < nCount; a++) + if((F_PI - fAngle) < fMiterMinimumAngle) { - // get left, right, prev and next positions for edge - B2DPoint aEdgeStart(rCandidate.getB2DPoint(a)); - B2DPoint aEdgeEnd(rCandidate.getB2DPoint((a + 1L) % nCount)); - - // create geometry for edge and add to result - B2DPolygon aEdgePolygon(createAreaGeometryForEdge( - aEdgeStart, aEdgeEnd, fHalfLineWidth, - bNeedToCreateJoinPolygon || bLastNeededToCreateJoinPolygon)); - aRetval.append(aEdgePolygon); - - if(bNeedToCreateJoinPolygon) - { - // create fill polygon for linejoin and add to result - B2DPoint aNextEdge(rCandidate.getB2DPoint((a + 2L) % nCount)); - B2DPolygon aJoinPolygon(createAreaGeometryForJoin( - aEdgeStart, aEdgeEnd, aNextEdge, fHalfLineWidth, eJoin, fDegreeStepWidth, fMiterMinimumAngle)); + // fallback to bevel + eJoin = B2DLINEJOIN_BEVEL; + } + } - if(aRetval.count()) - { - aRetval.append(aJoinPolygon); - } - } + // create first polygon part for edge + aEdgePolygon.append(aEndPoint); + aEdgePolygon.append(rPoint); + aEdgePolygon.append(aStartPoint); - bLastNeededToCreateJoinPolygon = bNeedToCreateJoinPolygon; + if(B2DLINEJOIN_MITER == eJoin) + { + // 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(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos)); + aEdgePolygon.append(aCutPoint); } } - else if(nCount > 1L) + else if(B2DLINEJOIN_ROUND == eJoin) { - bool bLastNeededToCreateJoinPolygon(false); + // use tooling to add needed EllipseSegment + double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX())); + double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX())); - for(sal_uInt32 a(0L); a < nCount - 1L; a++) + // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI] + if(fAngleStart < 0.0) { - // get left, right positions for edge - B2DPoint aEdgeStart(rCandidate.getB2DPoint(a)); - B2DPoint aEdgeEnd(rCandidate.getB2DPoint(a + 1L)); - const bool bNeedToCreateJoinPolygon((a + 2L < nCount) && B2DLINEJOIN_NONE != eJoin); - - // create geometry for edge and add to result - B2DPolygon aEdgePolygon( - createAreaGeometryForEdge(aEdgeStart, aEdgeEnd, fHalfLineWidth, - bNeedToCreateJoinPolygon || bLastNeededToCreateJoinPolygon)); - aRetval.append(aEdgePolygon); - - // test if next exists - if(bNeedToCreateJoinPolygon) - { - // create fill polygon for linejoin and add to result - B2DPoint aNextEdge(rCandidate.getB2DPoint((a + 2L))); - B2DPolygon aJoinPolygon(createAreaGeometryForJoin( - aEdgeStart, aEdgeEnd, aNextEdge, fHalfLineWidth, eJoin, fDegreeStepWidth, fMiterMinimumAngle)); - - if(aRetval.count()) - { - aRetval.append(aJoinPolygon); - } - } + fAngleStart += F_2PI; + } - bLastNeededToCreateJoinPolygon = bNeedToCreateJoinPolygon; + if(fAngleEnd < 0.0) + { + fAngleEnd += F_2PI; } + + aEdgePolygon.append(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd)); } - return aRetval; + // create last polygon part for edge + aEdgePolygon.setClosed(true); + + return aEdgePolygon; } + } // end of anonymus namespace - B2DPolyPolygon createAreaGeometryForLineStartEnd( + namespace tools + { + B2DPolyPolygon createAreaGeometry( const B2DPolygon& rCandidate, - const B2DPolyPolygon& rArrow, - bool bStart, - double fWidth, - double fDockingPosition, // 0->top, 1->bottom - double* pConsumedLength) + double fHalfLineWidth, + B2DLineJoin eJoin, + double fMaxAllowedAngle, + double fMaxPartOfEdge, + double fMiterMinimumAngle) { - OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points too small (!)"); - OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: No 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(fMaxAllowedAngle > F_PI2) + { + fMaxAllowedAngle = F_PI2; + } + else if(fMaxAllowedAngle < 0.01 * F_PI2) + { + fMaxAllowedAngle = 0.01 * F_PI2; + } - // init return value from arrow - B2DPolyPolygon aRetval(rArrow); + if(fMaxPartOfEdge > 1.0) + { + fMaxPartOfEdge = 1.0; + } + else if(fMaxPartOfEdge < 0.01) + { + fMaxPartOfEdge = 0.01; + } - // get size of the arrow - const B2DRange aArrowSize(getRange(rArrow)); + if(fMiterMinimumAngle > F_PI) + { + fMiterMinimumAngle = F_PI; + } + else if(fMiterMinimumAngle < 0.01 * F_PI) + { + fMiterMinimumAngle = 0.01 * F_PI; + } - // build ArrowTransform - B2DHomMatrix aArrowTransform; + B2DPolygon aCandidate(rCandidate); + const double fMaxCos(cos(fMaxAllowedAngle)); - // center in X, align with axis in Y - aArrowTransform.translate(-aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()); + aCandidate.removeDoublePoints(); + aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge); - // scale to target size - const double fArrowScale(fWidth / (aArrowSize.getRange().getX())); - aArrowTransform.scale(fArrowScale, fArrowScale); + const sal_uInt32 nPointCount(aCandidate.count()); - // get arrow size in Y - B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY()); - aUpperCenter *= aArrowTransform; - const double fArrowYLength(B2DVector(aUpperCenter).getLength()); + if(nPointCount) + { + B2DPolyPolygon aRetval; + const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin); + const bool bIsClosed(aCandidate.isClosed()); + const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1); - // move arrow to have docking position centered - aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition); + if(nEdgeCount) + { + B2DCubicBezier aEdge; + B2DCubicBezier aPrev; + + // prepare edge + aEdge.setStartPoint(aCandidate.getB2DPoint(0)); - // get the polygon vector we want to plant this arrow on - const double fCandidateLength(getLength(rCandidate)); - 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)); + 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)) + { + const B2DVector aTangentPrev(aPrev.getTangent(1.0)); + const B2DVector aTangentEdge(aEdge.getTangent(0.0)); + 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; + } + } + } - // 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)); + if(ORIENTATION_POSITIVE == aOrientation) + { + const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * -fHalfLineWidth); + const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * -fHalfLineWidth); + + aRetval.append(createAreaGeometryForJoin( + aTangentPrev, aTangentEdge, + aPerpendPrev, aPerpendEdge, + aEdge.getStartPoint(), fHalfLineWidth, + eJoin, fMiterMinimumAngle)); + } + else if(ORIENTATION_NEGATIVE == aOrientation) + { + const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * fHalfLineWidth); + const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * fHalfLineWidth); + + aRetval.append(createAreaGeometryForJoin( + aTangentEdge, aTangentPrev, + aPerpendEdge, aPerpendPrev, + aEdge.getStartPoint(), fHalfLineWidth, + eJoin, fMiterMinimumAngle)); + } + } - // rotate around docking position - aArrowTransform.rotate(fRotation); + // create geometry for edge + aRetval.append(createAreaGeometryForEdge(aEdge, fHalfLineWidth)); - // move arrow docking position to polygon head - aArrowTransform.translate(aHead.getX(), aHead.getY()); + // prepare next step + if(bEventuallyCreateLineJoin) + { + aPrev = aEdge; + } - // transform retval and close - aRetval.transform(aArrowTransform); - aRetval.setClosed(true); + aEdge.setStartPoint(aEdge.getEndPoint()); + } + } - // if pConsumedLength is asked for, fill it - if(pConsumedLength) + return aRetval; + } + else { - *pConsumedLength = fConsumedLength; + return B2DPolyPolygon(rCandidate); } - - return aRetval; } } // end of namespace tools } // end of namespace basegfx |