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