1*09dbbe93SAndrew Rist /************************************************************** 2cdf0e10cSrcweir * 3*09dbbe93SAndrew Rist * Licensed to the Apache Software Foundation (ASF) under one 4*09dbbe93SAndrew Rist * or more contributor license agreements. See the NOTICE file 5*09dbbe93SAndrew Rist * distributed with this work for additional information 6*09dbbe93SAndrew Rist * regarding copyright ownership. The ASF licenses this file 7*09dbbe93SAndrew Rist * to you under the Apache License, Version 2.0 (the 8*09dbbe93SAndrew Rist * "License"); you may not use this file except in compliance 9*09dbbe93SAndrew Rist * with the License. You may obtain a copy of the License at 10cdf0e10cSrcweir * 11*09dbbe93SAndrew Rist * http://www.apache.org/licenses/LICENSE-2.0 12cdf0e10cSrcweir * 13*09dbbe93SAndrew Rist * Unless required by applicable law or agreed to in writing, 14*09dbbe93SAndrew Rist * software distributed under the License is distributed on an 15*09dbbe93SAndrew Rist * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*09dbbe93SAndrew Rist * KIND, either express or implied. See the License for the 17*09dbbe93SAndrew Rist * specific language governing permissions and limitations 18*09dbbe93SAndrew Rist * under the License. 19cdf0e10cSrcweir * 20*09dbbe93SAndrew Rist *************************************************************/ 21*09dbbe93SAndrew Rist 22*09dbbe93SAndrew Rist 23cdf0e10cSrcweir 24cdf0e10cSrcweir // MARKER(update_precomp.py): autogen include statement, do not remove 25cdf0e10cSrcweir #include "precompiled_basegfx.hxx" 26cdf0e10cSrcweir #include <cstdio> 27cdf0e10cSrcweir #include <osl/diagnose.h> 28cdf0e10cSrcweir #include <basegfx/polygon/b2dlinegeometry.hxx> 29cdf0e10cSrcweir #include <basegfx/point/b2dpoint.hxx> 30cdf0e10cSrcweir #include <basegfx/vector/b2dvector.hxx> 31cdf0e10cSrcweir #include <basegfx/polygon/b2dpolygontools.hxx> 32cdf0e10cSrcweir #include <basegfx/polygon/b2dpolypolygontools.hxx> 33cdf0e10cSrcweir #include <basegfx/range/b2drange.hxx> 34cdf0e10cSrcweir #include <basegfx/matrix/b2dhommatrix.hxx> 35cdf0e10cSrcweir #include <basegfx/curve/b2dcubicbezier.hxx> 36cdf0e10cSrcweir #include <basegfx/matrix/b2dhommatrixtools.hxx> 37cdf0e10cSrcweir 38cdf0e10cSrcweir ////////////////////////////////////////////////////////////////////////////// 39cdf0e10cSrcweir 40cdf0e10cSrcweir namespace basegfx 41cdf0e10cSrcweir { 42cdf0e10cSrcweir namespace tools 43cdf0e10cSrcweir { 44cdf0e10cSrcweir B2DPolyPolygon createAreaGeometryForLineStartEnd( 45cdf0e10cSrcweir const B2DPolygon& rCandidate, 46cdf0e10cSrcweir const B2DPolyPolygon& rArrow, 47cdf0e10cSrcweir bool bStart, 48cdf0e10cSrcweir double fWidth, 49cdf0e10cSrcweir double fCandidateLength, 50cdf0e10cSrcweir double fDockingPosition, // 0->top, 1->bottom 51cdf0e10cSrcweir double* pConsumedLength) 52cdf0e10cSrcweir { 53cdf0e10cSrcweir B2DPolyPolygon aRetval; 54cdf0e10cSrcweir OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)"); 55cdf0e10cSrcweir OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)"); 56cdf0e10cSrcweir OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)"); 57cdf0e10cSrcweir OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0, 58cdf0e10cSrcweir "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)"); 59cdf0e10cSrcweir 60cdf0e10cSrcweir if(fWidth < 0.0) 61cdf0e10cSrcweir { 62cdf0e10cSrcweir fWidth = -fWidth; 63cdf0e10cSrcweir } 64cdf0e10cSrcweir 65cdf0e10cSrcweir if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth)) 66cdf0e10cSrcweir { 67cdf0e10cSrcweir if(fDockingPosition < 0.0) 68cdf0e10cSrcweir { 69cdf0e10cSrcweir fDockingPosition = 0.0; 70cdf0e10cSrcweir } 71cdf0e10cSrcweir else if(fDockingPosition > 1.0) 72cdf0e10cSrcweir { 73cdf0e10cSrcweir fDockingPosition = 1.0; 74cdf0e10cSrcweir } 75cdf0e10cSrcweir 76cdf0e10cSrcweir // init return value from arrow 77cdf0e10cSrcweir aRetval.append(rArrow); 78cdf0e10cSrcweir 79cdf0e10cSrcweir // get size of the arrow 80cdf0e10cSrcweir const B2DRange aArrowSize(getRange(rArrow)); 81cdf0e10cSrcweir 82cdf0e10cSrcweir // build ArrowTransform; center in X, align with axis in Y 83cdf0e10cSrcweir B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix( 84cdf0e10cSrcweir -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY())); 85cdf0e10cSrcweir 86cdf0e10cSrcweir // scale to target size 87cdf0e10cSrcweir const double fArrowScale(fWidth / (aArrowSize.getRange().getX())); 88cdf0e10cSrcweir aArrowTransform.scale(fArrowScale, fArrowScale); 89cdf0e10cSrcweir 90cdf0e10cSrcweir // get arrow size in Y 91cdf0e10cSrcweir B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY()); 92cdf0e10cSrcweir aUpperCenter *= aArrowTransform; 93cdf0e10cSrcweir const double fArrowYLength(B2DVector(aUpperCenter).getLength()); 94cdf0e10cSrcweir 95cdf0e10cSrcweir // move arrow to have docking position centered 96cdf0e10cSrcweir aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition); 97cdf0e10cSrcweir 98cdf0e10cSrcweir // prepare polygon length 99cdf0e10cSrcweir if(fTools::equalZero(fCandidateLength)) 100cdf0e10cSrcweir { 101cdf0e10cSrcweir fCandidateLength = getLength(rCandidate); 102cdf0e10cSrcweir } 103cdf0e10cSrcweir 104cdf0e10cSrcweir // get the polygon vector we want to plant this arrow on 105cdf0e10cSrcweir const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition)); 106cdf0e10cSrcweir const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L)); 107cdf0e10cSrcweir const B2DVector aTail(getPositionAbsolute(rCandidate, 108cdf0e10cSrcweir (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength)); 109cdf0e10cSrcweir 110cdf0e10cSrcweir // from that vector, take the needed rotation and add rotate for arrow to transformation 111cdf0e10cSrcweir const B2DVector aTargetDirection(aHead - aTail); 112cdf0e10cSrcweir const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180)); 113cdf0e10cSrcweir 114cdf0e10cSrcweir // rotate around docking position 115cdf0e10cSrcweir aArrowTransform.rotate(fRotation); 116cdf0e10cSrcweir 117cdf0e10cSrcweir // move arrow docking position to polygon head 118cdf0e10cSrcweir aArrowTransform.translate(aHead.getX(), aHead.getY()); 119cdf0e10cSrcweir 120cdf0e10cSrcweir // transform retval and close 121cdf0e10cSrcweir aRetval.transform(aArrowTransform); 122cdf0e10cSrcweir aRetval.setClosed(true); 123cdf0e10cSrcweir 124cdf0e10cSrcweir // if pConsumedLength is asked for, fill it 125cdf0e10cSrcweir if(pConsumedLength) 126cdf0e10cSrcweir { 127cdf0e10cSrcweir *pConsumedLength = fConsumedLength; 128cdf0e10cSrcweir } 129cdf0e10cSrcweir } 130cdf0e10cSrcweir 131cdf0e10cSrcweir return aRetval; 132cdf0e10cSrcweir } 133cdf0e10cSrcweir } // end of namespace tools 134cdf0e10cSrcweir } // end of namespace basegfx 135cdf0e10cSrcweir 136cdf0e10cSrcweir ////////////////////////////////////////////////////////////////////////////// 137cdf0e10cSrcweir 138cdf0e10cSrcweir namespace basegfx 139cdf0e10cSrcweir { 140cdf0e10cSrcweir // anonymus namespace for local helpers 141cdf0e10cSrcweir namespace 142cdf0e10cSrcweir { 143cdf0e10cSrcweir bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) 144cdf0e10cSrcweir { 145cdf0e10cSrcweir // isBezier() is true, already tested by caller 146cdf0e10cSrcweir const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint()); 147cdf0e10cSrcweir 148cdf0e10cSrcweir if(aEdge.equalZero()) 149cdf0e10cSrcweir { 150cdf0e10cSrcweir // start and end point the same, but control vectors used -> baloon curve loop 151cdf0e10cSrcweir // is not a simple edge 152cdf0e10cSrcweir return false; 153cdf0e10cSrcweir } 154cdf0e10cSrcweir 155cdf0e10cSrcweir // get tangentA and scalar with edge 156cdf0e10cSrcweir const B2DVector aTangentA(rCandidate.getTangent(0.0)); 157cdf0e10cSrcweir const double fScalarAE(aEdge.scalar(aTangentA)); 158cdf0e10cSrcweir 159cdf0e10cSrcweir if(fTools::lessOrEqual(fScalarAE, 0.0)) 160cdf0e10cSrcweir { 161cdf0e10cSrcweir // angle between TangentA and Edge is bigger or equal 90 degrees 162cdf0e10cSrcweir return false; 163cdf0e10cSrcweir } 164cdf0e10cSrcweir 165cdf0e10cSrcweir // get self-scalars for E and A 166cdf0e10cSrcweir const double fScalarE(aEdge.scalar(aEdge)); 167cdf0e10cSrcweir const double fScalarA(aTangentA.scalar(aTangentA)); 168cdf0e10cSrcweir const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad); 169cdf0e10cSrcweir 170cdf0e10cSrcweir if(fTools::moreOrEqual(fScalarA, fLengthCompareE)) 171cdf0e10cSrcweir { 172cdf0e10cSrcweir // length of TangentA is more than fMaxPartOfEdge of length of edge 173cdf0e10cSrcweir return false; 174cdf0e10cSrcweir } 175cdf0e10cSrcweir 176cdf0e10cSrcweir if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad)) 177cdf0e10cSrcweir { 178cdf0e10cSrcweir // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos 179cdf0e10cSrcweir return false; 180cdf0e10cSrcweir } 181cdf0e10cSrcweir 182cdf0e10cSrcweir // get tangentB and scalar with edge 183cdf0e10cSrcweir const B2DVector aTangentB(rCandidate.getTangent(1.0)); 184cdf0e10cSrcweir const double fScalarBE(aEdge.scalar(aTangentB)); 185cdf0e10cSrcweir 186cdf0e10cSrcweir if(fTools::lessOrEqual(fScalarBE, 0.0)) 187cdf0e10cSrcweir { 188cdf0e10cSrcweir // angle between TangentB and Edge is bigger or equal 90 degrees 189cdf0e10cSrcweir return false; 190cdf0e10cSrcweir } 191cdf0e10cSrcweir 192cdf0e10cSrcweir // get self-scalar for B 193cdf0e10cSrcweir const double fScalarB(aTangentB.scalar(aTangentB)); 194cdf0e10cSrcweir 195cdf0e10cSrcweir if(fTools::moreOrEqual(fScalarB, fLengthCompareE)) 196cdf0e10cSrcweir { 197cdf0e10cSrcweir // length of TangentB is more than fMaxPartOfEdge of length of edge 198cdf0e10cSrcweir return false; 199cdf0e10cSrcweir } 200cdf0e10cSrcweir 201cdf0e10cSrcweir if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad)) 202cdf0e10cSrcweir { 203cdf0e10cSrcweir // angle between TangentB and Edge is bigger or equal defined by fMaxCos 204cdf0e10cSrcweir return false; 205cdf0e10cSrcweir } 206cdf0e10cSrcweir 207cdf0e10cSrcweir return true; 208cdf0e10cSrcweir } 209cdf0e10cSrcweir 210cdf0e10cSrcweir void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth) 211cdf0e10cSrcweir { 212cdf0e10cSrcweir if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad)) 213cdf0e10cSrcweir { 214cdf0e10cSrcweir rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint()); 215cdf0e10cSrcweir } 216cdf0e10cSrcweir else 217cdf0e10cSrcweir { 218cdf0e10cSrcweir B2DCubicBezier aLeft, aRight; 219cdf0e10cSrcweir rCandidate.split(0.5, &aLeft, &aRight); 220cdf0e10cSrcweir 221cdf0e10cSrcweir impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); 222cdf0e10cSrcweir impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); 223cdf0e10cSrcweir } 224cdf0e10cSrcweir } 225cdf0e10cSrcweir 226cdf0e10cSrcweir B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) 227cdf0e10cSrcweir { 228cdf0e10cSrcweir const sal_uInt32 nPointCount(rCandidate.count()); 229cdf0e10cSrcweir 230cdf0e10cSrcweir if(rCandidate.areControlPointsUsed() && nPointCount) 231cdf0e10cSrcweir { 232cdf0e10cSrcweir const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); 233cdf0e10cSrcweir B2DPolygon aRetval; 234cdf0e10cSrcweir B2DCubicBezier aEdge; 235cdf0e10cSrcweir 236cdf0e10cSrcweir // prepare edge for loop 237cdf0e10cSrcweir aEdge.setStartPoint(rCandidate.getB2DPoint(0)); 238cdf0e10cSrcweir aRetval.append(aEdge.getStartPoint()); 239cdf0e10cSrcweir 240cdf0e10cSrcweir for(sal_uInt32 a(0); a < nEdgeCount; a++) 241cdf0e10cSrcweir { 242cdf0e10cSrcweir // fill B2DCubicBezier 243cdf0e10cSrcweir const sal_uInt32 nNextIndex((a + 1) % nPointCount); 244cdf0e10cSrcweir aEdge.setControlPointA(rCandidate.getNextControlPoint(a)); 245cdf0e10cSrcweir aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); 246cdf0e10cSrcweir aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); 247cdf0e10cSrcweir 248cdf0e10cSrcweir // get rid of unnecessary bezier segments 249cdf0e10cSrcweir aEdge.testAndSolveTrivialBezier(); 250cdf0e10cSrcweir 251cdf0e10cSrcweir if(aEdge.isBezier()) 252cdf0e10cSrcweir { 253cdf0e10cSrcweir // before splitting recursively with internal simple criteria, use 254cdf0e10cSrcweir // ExtremumPosFinder to remove those 255cdf0e10cSrcweir ::std::vector< double > aExtremumPositions; 256cdf0e10cSrcweir 257cdf0e10cSrcweir aExtremumPositions.reserve(4); 258cdf0e10cSrcweir aEdge.getAllExtremumPositions(aExtremumPositions); 259cdf0e10cSrcweir 260cdf0e10cSrcweir const sal_uInt32 nCount(aExtremumPositions.size()); 261cdf0e10cSrcweir 262cdf0e10cSrcweir if(nCount) 263cdf0e10cSrcweir { 264cdf0e10cSrcweir if(nCount > 1) 265cdf0e10cSrcweir { 266cdf0e10cSrcweir // create order from left to right 267cdf0e10cSrcweir ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end()); 268cdf0e10cSrcweir } 269cdf0e10cSrcweir 270cdf0e10cSrcweir for(sal_uInt32 b(0); b < nCount;) 271cdf0e10cSrcweir { 272cdf0e10cSrcweir // split aEdge at next split pos 273cdf0e10cSrcweir B2DCubicBezier aLeft; 274cdf0e10cSrcweir const double fSplitPos(aExtremumPositions[b++]); 275cdf0e10cSrcweir 276cdf0e10cSrcweir aEdge.split(fSplitPos, &aLeft, &aEdge); 277cdf0e10cSrcweir aLeft.testAndSolveTrivialBezier(); 278cdf0e10cSrcweir 279cdf0e10cSrcweir // consume left part 280cdf0e10cSrcweir if(aLeft.isBezier()) 281cdf0e10cSrcweir { 282cdf0e10cSrcweir impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 283cdf0e10cSrcweir } 284cdf0e10cSrcweir else 285cdf0e10cSrcweir { 286cdf0e10cSrcweir aRetval.append(aLeft.getEndPoint()); 287cdf0e10cSrcweir } 288cdf0e10cSrcweir 289cdf0e10cSrcweir if(b < nCount) 290cdf0e10cSrcweir { 291cdf0e10cSrcweir // correct the remaining split positions to fit to shortened aEdge 292cdf0e10cSrcweir const double fScaleFactor(1.0 / (1.0 - fSplitPos)); 293cdf0e10cSrcweir 294cdf0e10cSrcweir for(sal_uInt32 c(b); c < nCount; c++) 295cdf0e10cSrcweir { 296cdf0e10cSrcweir aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor; 297cdf0e10cSrcweir } 298cdf0e10cSrcweir } 299cdf0e10cSrcweir } 300cdf0e10cSrcweir 301cdf0e10cSrcweir // test the shortened rest of aEdge 302cdf0e10cSrcweir aEdge.testAndSolveTrivialBezier(); 303cdf0e10cSrcweir 304cdf0e10cSrcweir // consume right part 305cdf0e10cSrcweir if(aEdge.isBezier()) 306cdf0e10cSrcweir { 307cdf0e10cSrcweir impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 308cdf0e10cSrcweir } 309cdf0e10cSrcweir else 310cdf0e10cSrcweir { 311cdf0e10cSrcweir aRetval.append(aEdge.getEndPoint()); 312cdf0e10cSrcweir } 313cdf0e10cSrcweir } 314cdf0e10cSrcweir else 315cdf0e10cSrcweir { 316cdf0e10cSrcweir impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 317cdf0e10cSrcweir } 318cdf0e10cSrcweir } 319cdf0e10cSrcweir else 320cdf0e10cSrcweir { 321cdf0e10cSrcweir // straight edge, add point 322cdf0e10cSrcweir aRetval.append(aEdge.getEndPoint()); 323cdf0e10cSrcweir } 324cdf0e10cSrcweir 325cdf0e10cSrcweir // prepare edge for next step 326cdf0e10cSrcweir aEdge.setStartPoint(aEdge.getEndPoint()); 327cdf0e10cSrcweir } 328cdf0e10cSrcweir 329cdf0e10cSrcweir // copy closed flag and check for double points 330cdf0e10cSrcweir aRetval.setClosed(rCandidate.isClosed()); 331cdf0e10cSrcweir aRetval.removeDoublePoints(); 332cdf0e10cSrcweir 333cdf0e10cSrcweir return aRetval; 334cdf0e10cSrcweir } 335cdf0e10cSrcweir else 336cdf0e10cSrcweir { 337cdf0e10cSrcweir return rCandidate; 338cdf0e10cSrcweir } 339cdf0e10cSrcweir } 340cdf0e10cSrcweir 341cdf0e10cSrcweir B2DPolygon createAreaGeometryForEdge(const B2DCubicBezier& rEdge, double fHalfLineWidth) 342cdf0e10cSrcweir { 343cdf0e10cSrcweir // create polygon for edge 344cdf0e10cSrcweir // Unfortunately, while it would be geometrically correct to not add 345cdf0e10cSrcweir // the in-between points EdgeEnd and EdgeStart, it leads to rounding 346cdf0e10cSrcweir // errors when converting to integer polygon coordinates for painting 347cdf0e10cSrcweir if(rEdge.isBezier()) 348cdf0e10cSrcweir { 349cdf0e10cSrcweir // prepare target and data common for upper and lower 350cdf0e10cSrcweir B2DPolygon aBezierPolygon; 351cdf0e10cSrcweir const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint()); 352cdf0e10cSrcweir const double fEdgeLength(aPureEdgeVector.getLength()); 353cdf0e10cSrcweir const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength)); 354cdf0e10cSrcweir const B2DVector aTangentA(rEdge.getTangent(0.0)); 355cdf0e10cSrcweir const B2DVector aTangentB(rEdge.getTangent(1.0)); 356cdf0e10cSrcweir 357cdf0e10cSrcweir // create upper edge. 358cdf0e10cSrcweir { 359cdf0e10cSrcweir // create displacement vectors and check if they cut 360cdf0e10cSrcweir const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * -fHalfLineWidth); 361cdf0e10cSrcweir const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * -fHalfLineWidth); 362cdf0e10cSrcweir double fCut(0.0); 363cdf0e10cSrcweir const tools::CutFlagValue aCut(tools::findCut( 364cdf0e10cSrcweir rEdge.getStartPoint(), aPerpendStart, 365cdf0e10cSrcweir rEdge.getEndPoint(), aPerpendEnd, 366cdf0e10cSrcweir CUTFLAG_ALL, &fCut)); 367cdf0e10cSrcweir 368cdf0e10cSrcweir if(CUTFLAG_NONE != aCut) 369cdf0e10cSrcweir { 370cdf0e10cSrcweir // calculate cut point and add 371cdf0e10cSrcweir const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStart * fCut)); 372cdf0e10cSrcweir aBezierPolygon.append(aCutPoint); 373cdf0e10cSrcweir } 374cdf0e10cSrcweir else 375cdf0e10cSrcweir { 376cdf0e10cSrcweir // create scaled bezier segment 377cdf0e10cSrcweir const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStart); 378cdf0e10cSrcweir const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEnd); 379cdf0e10cSrcweir const B2DVector aEdge(aEnd - aStart); 380cdf0e10cSrcweir const double fLength(aEdge.getLength()); 381cdf0e10cSrcweir const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength); 382cdf0e10cSrcweir const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint()); 383cdf0e10cSrcweir const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint()); 384cdf0e10cSrcweir 385cdf0e10cSrcweir aBezierPolygon.append(aStart); 386cdf0e10cSrcweir aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd); 387cdf0e10cSrcweir } 388cdf0e10cSrcweir } 389cdf0e10cSrcweir 390cdf0e10cSrcweir // append original in-between point 391cdf0e10cSrcweir aBezierPolygon.append(rEdge.getEndPoint()); 392cdf0e10cSrcweir 393cdf0e10cSrcweir // create lower edge. 394cdf0e10cSrcweir { 395cdf0e10cSrcweir // create displacement vectors and check if they cut 396cdf0e10cSrcweir const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * fHalfLineWidth); 397cdf0e10cSrcweir const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * fHalfLineWidth); 398cdf0e10cSrcweir double fCut(0.0); 399cdf0e10cSrcweir const tools::CutFlagValue aCut(tools::findCut( 400cdf0e10cSrcweir rEdge.getEndPoint(), aPerpendEnd, 401cdf0e10cSrcweir rEdge.getStartPoint(), aPerpendStart, 402cdf0e10cSrcweir CUTFLAG_ALL, &fCut)); 403cdf0e10cSrcweir 404cdf0e10cSrcweir if(CUTFLAG_NONE != aCut) 405cdf0e10cSrcweir { 406cdf0e10cSrcweir // calculate cut point and add 407cdf0e10cSrcweir const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEnd * fCut)); 408cdf0e10cSrcweir aBezierPolygon.append(aCutPoint); 409cdf0e10cSrcweir } 410cdf0e10cSrcweir else 411cdf0e10cSrcweir { 412cdf0e10cSrcweir // create scaled bezier segment 413cdf0e10cSrcweir const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEnd); 414cdf0e10cSrcweir const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStart); 415cdf0e10cSrcweir const B2DVector aEdge(aEnd - aStart); 416cdf0e10cSrcweir const double fLength(aEdge.getLength()); 417cdf0e10cSrcweir const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength); 418cdf0e10cSrcweir const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint()); 419cdf0e10cSrcweir const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint()); 420cdf0e10cSrcweir 421cdf0e10cSrcweir aBezierPolygon.append(aStart); 422cdf0e10cSrcweir aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd); 423cdf0e10cSrcweir } 424cdf0e10cSrcweir } 425cdf0e10cSrcweir 426cdf0e10cSrcweir // append original in-between point 427cdf0e10cSrcweir aBezierPolygon.append(rEdge.getStartPoint()); 428cdf0e10cSrcweir 429cdf0e10cSrcweir // close and return 430cdf0e10cSrcweir aBezierPolygon.setClosed(true); 431cdf0e10cSrcweir return aBezierPolygon; 432cdf0e10cSrcweir } 433cdf0e10cSrcweir else 434cdf0e10cSrcweir { 435cdf0e10cSrcweir // #i101491# emulate rEdge.getTangent call which applies a factor of 0.3 to the 436cdf0e10cSrcweir // full-length edge vector to have numerically exactly the same results as in the 437cdf0e10cSrcweir // createAreaGeometryForJoin implementation 438cdf0e10cSrcweir const B2DVector aEdgeTangent((rEdge.getEndPoint() - rEdge.getStartPoint()) * 0.3); 439cdf0e10cSrcweir const B2DVector aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeTangent) * fHalfLineWidth); 440cdf0e10cSrcweir B2DPolygon aEdgePolygon; 441cdf0e10cSrcweir 442cdf0e10cSrcweir // create upper edge 443cdf0e10cSrcweir aEdgePolygon.append(rEdge.getStartPoint() - aPerpendEdgeVector); 444cdf0e10cSrcweir aEdgePolygon.append(rEdge.getEndPoint() - aPerpendEdgeVector); 445cdf0e10cSrcweir 446cdf0e10cSrcweir // append original in-between point 447cdf0e10cSrcweir aEdgePolygon.append(rEdge.getEndPoint()); 448cdf0e10cSrcweir 449cdf0e10cSrcweir // create lower edge 450cdf0e10cSrcweir aEdgePolygon.append(rEdge.getEndPoint() + aPerpendEdgeVector); 451cdf0e10cSrcweir aEdgePolygon.append(rEdge.getStartPoint() + aPerpendEdgeVector); 452cdf0e10cSrcweir 453cdf0e10cSrcweir // append original in-between point 454cdf0e10cSrcweir aEdgePolygon.append(rEdge.getStartPoint()); 455cdf0e10cSrcweir 456cdf0e10cSrcweir // close and return 457cdf0e10cSrcweir aEdgePolygon.setClosed(true); 458cdf0e10cSrcweir return aEdgePolygon; 459cdf0e10cSrcweir } 460cdf0e10cSrcweir } 461cdf0e10cSrcweir 462cdf0e10cSrcweir B2DPolygon createAreaGeometryForJoin( 463cdf0e10cSrcweir const B2DVector& rTangentPrev, 464cdf0e10cSrcweir const B2DVector& rTangentEdge, 465cdf0e10cSrcweir const B2DVector& rPerpendPrev, 466cdf0e10cSrcweir const B2DVector& rPerpendEdge, 467cdf0e10cSrcweir const B2DPoint& rPoint, 468cdf0e10cSrcweir double fHalfLineWidth, 469cdf0e10cSrcweir B2DLineJoin eJoin, 470cdf0e10cSrcweir double fMiterMinimumAngle) 471cdf0e10cSrcweir { 472cdf0e10cSrcweir OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)"); 473cdf0e10cSrcweir OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)"); 474cdf0e10cSrcweir 475cdf0e10cSrcweir // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint 476cdf0e10cSrcweir B2DPolygon aEdgePolygon; 477cdf0e10cSrcweir const B2DPoint aStartPoint(rPoint + rPerpendPrev); 478cdf0e10cSrcweir const B2DPoint aEndPoint(rPoint + rPerpendEdge); 479cdf0e10cSrcweir 480cdf0e10cSrcweir // test if for Miter, the angle is too small and the fallback 481cdf0e10cSrcweir // to bevel needs to be used 482cdf0e10cSrcweir if(B2DLINEJOIN_MITER == eJoin) 483cdf0e10cSrcweir { 484cdf0e10cSrcweir const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge))); 485cdf0e10cSrcweir 486cdf0e10cSrcweir if((F_PI - fAngle) < fMiterMinimumAngle) 487cdf0e10cSrcweir { 488cdf0e10cSrcweir // fallback to bevel 489cdf0e10cSrcweir eJoin = B2DLINEJOIN_BEVEL; 490cdf0e10cSrcweir } 491cdf0e10cSrcweir } 492cdf0e10cSrcweir 493cdf0e10cSrcweir switch(eJoin) 494cdf0e10cSrcweir { 495cdf0e10cSrcweir case B2DLINEJOIN_MITER : 496cdf0e10cSrcweir { 497cdf0e10cSrcweir aEdgePolygon.append(aEndPoint); 498cdf0e10cSrcweir aEdgePolygon.append(rPoint); 499cdf0e10cSrcweir aEdgePolygon.append(aStartPoint); 500cdf0e10cSrcweir 501cdf0e10cSrcweir // Look for the cut point between start point along rTangentPrev and 502cdf0e10cSrcweir // end point along rTangentEdge. -rTangentEdge should be used, but since 503cdf0e10cSrcweir // the cut value is used for interpolating along the first edge, the negation 504cdf0e10cSrcweir // is not needed since the same fCut will be found on the first edge. 505cdf0e10cSrcweir // If it exists, insert it to complete the mitered fill polygon. 506cdf0e10cSrcweir double fCutPos(0.0); 507cdf0e10cSrcweir tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos); 508cdf0e10cSrcweir 509cdf0e10cSrcweir if(0.0 != fCutPos) 510cdf0e10cSrcweir { 511cdf0e10cSrcweir const B2DPoint aCutPoint(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos)); 512cdf0e10cSrcweir aEdgePolygon.append(aCutPoint); 513cdf0e10cSrcweir } 514cdf0e10cSrcweir 515cdf0e10cSrcweir break; 516cdf0e10cSrcweir } 517cdf0e10cSrcweir case B2DLINEJOIN_ROUND : 518cdf0e10cSrcweir { 519cdf0e10cSrcweir // use tooling to add needed EllipseSegment 520cdf0e10cSrcweir double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX())); 521cdf0e10cSrcweir double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX())); 522cdf0e10cSrcweir 523cdf0e10cSrcweir // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI] 524cdf0e10cSrcweir if(fAngleStart < 0.0) 525cdf0e10cSrcweir { 526cdf0e10cSrcweir fAngleStart += F_2PI; 527cdf0e10cSrcweir } 528cdf0e10cSrcweir 529cdf0e10cSrcweir if(fAngleEnd < 0.0) 530cdf0e10cSrcweir { 531cdf0e10cSrcweir fAngleEnd += F_2PI; 532cdf0e10cSrcweir } 533cdf0e10cSrcweir 534cdf0e10cSrcweir const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd)); 535cdf0e10cSrcweir 536cdf0e10cSrcweir if(aBow.count() > 1) 537cdf0e10cSrcweir { 538cdf0e10cSrcweir // #i101491# 539cdf0e10cSrcweir // use the original start/end positions; the ones from bow creation may be numerically 540cdf0e10cSrcweir // different due to their different creation. To guarantee good merging quality with edges 541cdf0e10cSrcweir // and edge roundings (and to reduce point count) 542cdf0e10cSrcweir aEdgePolygon = aBow; 543cdf0e10cSrcweir aEdgePolygon.setB2DPoint(0, aStartPoint); 544cdf0e10cSrcweir aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint); 545cdf0e10cSrcweir aEdgePolygon.append(rPoint); 546cdf0e10cSrcweir 547cdf0e10cSrcweir break; 548cdf0e10cSrcweir } 549cdf0e10cSrcweir else 550cdf0e10cSrcweir { 551cdf0e10cSrcweir // wanted fall-through to default 552cdf0e10cSrcweir } 553cdf0e10cSrcweir } 554cdf0e10cSrcweir default: // B2DLINEJOIN_BEVEL 555cdf0e10cSrcweir { 556cdf0e10cSrcweir aEdgePolygon.append(aEndPoint); 557cdf0e10cSrcweir aEdgePolygon.append(rPoint); 558cdf0e10cSrcweir aEdgePolygon.append(aStartPoint); 559cdf0e10cSrcweir 560cdf0e10cSrcweir break; 561cdf0e10cSrcweir } 562cdf0e10cSrcweir } 563cdf0e10cSrcweir 564cdf0e10cSrcweir // create last polygon part for edge 565cdf0e10cSrcweir aEdgePolygon.setClosed(true); 566cdf0e10cSrcweir 567cdf0e10cSrcweir return aEdgePolygon; 568cdf0e10cSrcweir } 569cdf0e10cSrcweir } // end of anonymus namespace 570cdf0e10cSrcweir 571cdf0e10cSrcweir namespace tools 572cdf0e10cSrcweir { 573cdf0e10cSrcweir B2DPolyPolygon createAreaGeometry( 574cdf0e10cSrcweir const B2DPolygon& rCandidate, 575cdf0e10cSrcweir double fHalfLineWidth, 576cdf0e10cSrcweir B2DLineJoin eJoin, 577cdf0e10cSrcweir double fMaxAllowedAngle, 578cdf0e10cSrcweir double fMaxPartOfEdge, 579cdf0e10cSrcweir double fMiterMinimumAngle) 580cdf0e10cSrcweir { 581cdf0e10cSrcweir if(fMaxAllowedAngle > F_PI2) 582cdf0e10cSrcweir { 583cdf0e10cSrcweir fMaxAllowedAngle = F_PI2; 584cdf0e10cSrcweir } 585cdf0e10cSrcweir else if(fMaxAllowedAngle < 0.01 * F_PI2) 586cdf0e10cSrcweir { 587cdf0e10cSrcweir fMaxAllowedAngle = 0.01 * F_PI2; 588cdf0e10cSrcweir } 589cdf0e10cSrcweir 590cdf0e10cSrcweir if(fMaxPartOfEdge > 1.0) 591cdf0e10cSrcweir { 592cdf0e10cSrcweir fMaxPartOfEdge = 1.0; 593cdf0e10cSrcweir } 594cdf0e10cSrcweir else if(fMaxPartOfEdge < 0.01) 595cdf0e10cSrcweir { 596cdf0e10cSrcweir fMaxPartOfEdge = 0.01; 597cdf0e10cSrcweir } 598cdf0e10cSrcweir 599cdf0e10cSrcweir if(fMiterMinimumAngle > F_PI) 600cdf0e10cSrcweir { 601cdf0e10cSrcweir fMiterMinimumAngle = F_PI; 602cdf0e10cSrcweir } 603cdf0e10cSrcweir else if(fMiterMinimumAngle < 0.01 * F_PI) 604cdf0e10cSrcweir { 605cdf0e10cSrcweir fMiterMinimumAngle = 0.01 * F_PI; 606cdf0e10cSrcweir } 607cdf0e10cSrcweir 608cdf0e10cSrcweir B2DPolygon aCandidate(rCandidate); 609cdf0e10cSrcweir const double fMaxCos(cos(fMaxAllowedAngle)); 610cdf0e10cSrcweir 611cdf0e10cSrcweir aCandidate.removeDoublePoints(); 612cdf0e10cSrcweir aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge); 613cdf0e10cSrcweir 614cdf0e10cSrcweir const sal_uInt32 nPointCount(aCandidate.count()); 615cdf0e10cSrcweir 616cdf0e10cSrcweir if(nPointCount) 617cdf0e10cSrcweir { 618cdf0e10cSrcweir B2DPolyPolygon aRetval; 619cdf0e10cSrcweir const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin); 620cdf0e10cSrcweir const bool bIsClosed(aCandidate.isClosed()); 621cdf0e10cSrcweir const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1); 622cdf0e10cSrcweir 623cdf0e10cSrcweir if(nEdgeCount) 624cdf0e10cSrcweir { 625cdf0e10cSrcweir B2DCubicBezier aEdge; 626cdf0e10cSrcweir B2DCubicBezier aPrev; 627cdf0e10cSrcweir 628cdf0e10cSrcweir // prepare edge 629cdf0e10cSrcweir aEdge.setStartPoint(aCandidate.getB2DPoint(0)); 630cdf0e10cSrcweir 631cdf0e10cSrcweir if(bIsClosed && bEventuallyCreateLineJoin) 632cdf0e10cSrcweir { 633cdf0e10cSrcweir // prepare previous edge 634cdf0e10cSrcweir const sal_uInt32 nPrevIndex(nPointCount - 1); 635cdf0e10cSrcweir aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex)); 636cdf0e10cSrcweir aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex)); 637cdf0e10cSrcweir aPrev.setControlPointB(aCandidate.getPrevControlPoint(0)); 638cdf0e10cSrcweir aPrev.setEndPoint(aEdge.getStartPoint()); 639cdf0e10cSrcweir } 640cdf0e10cSrcweir 641cdf0e10cSrcweir for(sal_uInt32 a(0); a < nEdgeCount; a++) 642cdf0e10cSrcweir { 643cdf0e10cSrcweir // fill current Edge 644cdf0e10cSrcweir const sal_uInt32 nNextIndex((a + 1) % nPointCount); 645cdf0e10cSrcweir aEdge.setControlPointA(aCandidate.getNextControlPoint(a)); 646cdf0e10cSrcweir aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex)); 647cdf0e10cSrcweir aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex)); 648cdf0e10cSrcweir 649cdf0e10cSrcweir // check and create linejoin 650cdf0e10cSrcweir if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a)) 651cdf0e10cSrcweir { 652cdf0e10cSrcweir const B2DVector aTangentPrev(aPrev.getTangent(1.0)); 653cdf0e10cSrcweir const B2DVector aTangentEdge(aEdge.getTangent(0.0)); 654cdf0e10cSrcweir B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge)); 655cdf0e10cSrcweir 656cdf0e10cSrcweir if(ORIENTATION_NEUTRAL == aOrientation) 657cdf0e10cSrcweir { 658cdf0e10cSrcweir // they are parallell or empty; if they are both not zero and point 659cdf0e10cSrcweir // in opposite direction, a half-circle is needed 660cdf0e10cSrcweir if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero()) 661cdf0e10cSrcweir { 662cdf0e10cSrcweir const double fAngle(fabs(aTangentPrev.angle(aTangentEdge))); 663cdf0e10cSrcweir 664cdf0e10cSrcweir if(fTools::equal(fAngle, F_PI)) 665cdf0e10cSrcweir { 666cdf0e10cSrcweir // for half-circle production, fallback to positive 667cdf0e10cSrcweir // orientation 668cdf0e10cSrcweir aOrientation = ORIENTATION_POSITIVE; 669cdf0e10cSrcweir } 670cdf0e10cSrcweir } 671cdf0e10cSrcweir } 672cdf0e10cSrcweir 673cdf0e10cSrcweir if(ORIENTATION_POSITIVE == aOrientation) 674cdf0e10cSrcweir { 675cdf0e10cSrcweir const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * -fHalfLineWidth); 676cdf0e10cSrcweir const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * -fHalfLineWidth); 677cdf0e10cSrcweir 678cdf0e10cSrcweir aRetval.append(createAreaGeometryForJoin( 679cdf0e10cSrcweir aTangentPrev, aTangentEdge, 680cdf0e10cSrcweir aPerpendPrev, aPerpendEdge, 681cdf0e10cSrcweir aEdge.getStartPoint(), fHalfLineWidth, 682cdf0e10cSrcweir eJoin, fMiterMinimumAngle)); 683cdf0e10cSrcweir } 684cdf0e10cSrcweir else if(ORIENTATION_NEGATIVE == aOrientation) 685cdf0e10cSrcweir { 686cdf0e10cSrcweir const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * fHalfLineWidth); 687cdf0e10cSrcweir const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * fHalfLineWidth); 688cdf0e10cSrcweir 689cdf0e10cSrcweir aRetval.append(createAreaGeometryForJoin( 690cdf0e10cSrcweir aTangentEdge, aTangentPrev, 691cdf0e10cSrcweir aPerpendEdge, aPerpendPrev, 692cdf0e10cSrcweir aEdge.getStartPoint(), fHalfLineWidth, 693cdf0e10cSrcweir eJoin, fMiterMinimumAngle)); 694cdf0e10cSrcweir } 695cdf0e10cSrcweir } 696cdf0e10cSrcweir 697cdf0e10cSrcweir // create geometry for edge 698cdf0e10cSrcweir aRetval.append(createAreaGeometryForEdge(aEdge, fHalfLineWidth)); 699cdf0e10cSrcweir 700cdf0e10cSrcweir // prepare next step 701cdf0e10cSrcweir if(bEventuallyCreateLineJoin) 702cdf0e10cSrcweir { 703cdf0e10cSrcweir aPrev = aEdge; 704cdf0e10cSrcweir } 705cdf0e10cSrcweir 706cdf0e10cSrcweir aEdge.setStartPoint(aEdge.getEndPoint()); 707cdf0e10cSrcweir } 708cdf0e10cSrcweir } 709cdf0e10cSrcweir 710cdf0e10cSrcweir return aRetval; 711cdf0e10cSrcweir } 712cdf0e10cSrcweir else 713cdf0e10cSrcweir { 714cdf0e10cSrcweir return B2DPolyPolygon(rCandidate); 715cdf0e10cSrcweir } 716cdf0e10cSrcweir } 717cdf0e10cSrcweir } // end of namespace tools 718cdf0e10cSrcweir } // end of namespace basegfx 719cdf0e10cSrcweir 720cdf0e10cSrcweir ////////////////////////////////////////////////////////////////////////////// 721cdf0e10cSrcweir // eof 722