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