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 #include <com/sun/star/drawing/LineCap.hpp> 38 39 ////////////////////////////////////////////////////////////////////////////// 40 41 namespace basegfx 42 { 43 namespace tools 44 { 45 B2DPolyPolygon createAreaGeometryForLineStartEnd( 46 const B2DPolygon& rCandidate, 47 const B2DPolyPolygon& rArrow, 48 bool bStart, 49 double fWidth, 50 double fCandidateLength, 51 double fDockingPosition, // 0->top, 1->bottom 52 double* pConsumedLength) 53 { 54 B2DPolyPolygon aRetval; 55 OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)"); 56 OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)"); 57 OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)"); 58 OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0, 59 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)"); 60 61 if(fWidth < 0.0) 62 { 63 fWidth = -fWidth; 64 } 65 66 if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth)) 67 { 68 if(fDockingPosition < 0.0) 69 { 70 fDockingPosition = 0.0; 71 } 72 else if(fDockingPosition > 1.0) 73 { 74 fDockingPosition = 1.0; 75 } 76 77 // init return value from arrow 78 aRetval.append(rArrow); 79 80 // get size of the arrow 81 const B2DRange aArrowSize(getRange(rArrow)); 82 83 // build ArrowTransform; center in X, align with axis in Y 84 B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix( 85 -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY())); 86 87 // scale to target size 88 const double fArrowScale(fWidth / (aArrowSize.getRange().getX())); 89 aArrowTransform.scale(fArrowScale, fArrowScale); 90 91 // get arrow size in Y 92 B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY()); 93 aUpperCenter *= aArrowTransform; 94 const double fArrowYLength(B2DVector(aUpperCenter).getLength()); 95 96 // move arrow to have docking position centered 97 aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition); 98 99 // prepare polygon length 100 if(fTools::equalZero(fCandidateLength)) 101 { 102 fCandidateLength = getLength(rCandidate); 103 } 104 105 // get the polygon vector we want to plant this arrow on 106 const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition)); 107 const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L)); 108 const B2DVector aTail(getPositionAbsolute(rCandidate, 109 (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength)); 110 111 // from that vector, take the needed rotation and add rotate for arrow to transformation 112 const B2DVector aTargetDirection(aHead - aTail); 113 const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180)); 114 115 // rotate around docking position 116 aArrowTransform.rotate(fRotation); 117 118 // move arrow docking position to polygon head 119 aArrowTransform.translate(aHead.getX(), aHead.getY()); 120 121 // transform retval and close 122 aRetval.transform(aArrowTransform); 123 aRetval.setClosed(true); 124 125 // if pConsumedLength is asked for, fill it 126 if(pConsumedLength) 127 { 128 *pConsumedLength = fConsumedLength; 129 } 130 } 131 132 return aRetval; 133 } 134 } // end of namespace tools 135 } // end of namespace basegfx 136 137 ////////////////////////////////////////////////////////////////////////////// 138 139 namespace basegfx 140 { 141 // anonymus namespace for local helpers 142 namespace 143 { 144 bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) 145 { 146 // isBezier() is true, already tested by caller 147 const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint()); 148 149 if(aEdge.equalZero()) 150 { 151 // start and end point the same, but control vectors used -> baloon curve loop 152 // is not a simple edge 153 return false; 154 } 155 156 // get tangentA and scalar with edge 157 const B2DVector aTangentA(rCandidate.getTangent(0.0)); 158 const double fScalarAE(aEdge.scalar(aTangentA)); 159 160 if(fTools::lessOrEqual(fScalarAE, 0.0)) 161 { 162 // angle between TangentA and Edge is bigger or equal 90 degrees 163 return false; 164 } 165 166 // get self-scalars for E and A 167 const double fScalarE(aEdge.scalar(aEdge)); 168 const double fScalarA(aTangentA.scalar(aTangentA)); 169 const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad); 170 171 if(fTools::moreOrEqual(fScalarA, fLengthCompareE)) 172 { 173 // length of TangentA is more than fMaxPartOfEdge of length of edge 174 return false; 175 } 176 177 if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad)) 178 { 179 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos 180 return false; 181 } 182 183 // get tangentB and scalar with edge 184 const B2DVector aTangentB(rCandidate.getTangent(1.0)); 185 const double fScalarBE(aEdge.scalar(aTangentB)); 186 187 if(fTools::lessOrEqual(fScalarBE, 0.0)) 188 { 189 // angle between TangentB and Edge is bigger or equal 90 degrees 190 return false; 191 } 192 193 // get self-scalar for B 194 const double fScalarB(aTangentB.scalar(aTangentB)); 195 196 if(fTools::moreOrEqual(fScalarB, fLengthCompareE)) 197 { 198 // length of TangentB is more than fMaxPartOfEdge of length of edge 199 return false; 200 } 201 202 if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad)) 203 { 204 // angle between TangentB and Edge is bigger or equal defined by fMaxCos 205 return false; 206 } 207 208 return true; 209 } 210 211 void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth) 212 { 213 if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad)) 214 { 215 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint()); 216 } 217 else 218 { 219 B2DCubicBezier aLeft, aRight; 220 rCandidate.split(0.5, &aLeft, &aRight); 221 222 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); 223 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1); 224 } 225 } 226 227 B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad) 228 { 229 const sal_uInt32 nPointCount(rCandidate.count()); 230 231 if(rCandidate.areControlPointsUsed() && nPointCount) 232 { 233 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); 234 B2DPolygon aRetval; 235 B2DCubicBezier aEdge; 236 237 // prepare edge for loop 238 aEdge.setStartPoint(rCandidate.getB2DPoint(0)); 239 aRetval.append(aEdge.getStartPoint()); 240 241 for(sal_uInt32 a(0); a < nEdgeCount; a++) 242 { 243 // fill B2DCubicBezier 244 const sal_uInt32 nNextIndex((a + 1) % nPointCount); 245 aEdge.setControlPointA(rCandidate.getNextControlPoint(a)); 246 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); 247 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); 248 249 // get rid of unnecessary bezier segments 250 aEdge.testAndSolveTrivialBezier(); 251 252 if(aEdge.isBezier()) 253 { 254 // before splitting recursively with internal simple criteria, use 255 // ExtremumPosFinder to remove those 256 ::std::vector< double > aExtremumPositions; 257 258 aExtremumPositions.reserve(4); 259 aEdge.getAllExtremumPositions(aExtremumPositions); 260 261 const sal_uInt32 nCount(aExtremumPositions.size()); 262 263 if(nCount) 264 { 265 if(nCount > 1) 266 { 267 // create order from left to right 268 ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end()); 269 } 270 271 for(sal_uInt32 b(0); b < nCount;) 272 { 273 // split aEdge at next split pos 274 B2DCubicBezier aLeft; 275 const double fSplitPos(aExtremumPositions[b++]); 276 277 aEdge.split(fSplitPos, &aLeft, &aEdge); 278 aLeft.testAndSolveTrivialBezier(); 279 280 // consume left part 281 if(aLeft.isBezier()) 282 { 283 impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 284 } 285 else 286 { 287 aRetval.append(aLeft.getEndPoint()); 288 } 289 290 if(b < nCount) 291 { 292 // correct the remaining split positions to fit to shortened aEdge 293 const double fScaleFactor(1.0 / (1.0 - fSplitPos)); 294 295 for(sal_uInt32 c(b); c < nCount; c++) 296 { 297 aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor; 298 } 299 } 300 } 301 302 // test the shortened rest of aEdge 303 aEdge.testAndSolveTrivialBezier(); 304 305 // consume right part 306 if(aEdge.isBezier()) 307 { 308 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 309 } 310 else 311 { 312 aRetval.append(aEdge.getEndPoint()); 313 } 314 } 315 else 316 { 317 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6); 318 } 319 } 320 else 321 { 322 // straight edge, add point 323 aRetval.append(aEdge.getEndPoint()); 324 } 325 326 // prepare edge for next step 327 aEdge.setStartPoint(aEdge.getEndPoint()); 328 } 329 330 // copy closed flag and check for double points 331 aRetval.setClosed(rCandidate.isClosed()); 332 aRetval.removeDoublePoints(); 333 334 return aRetval; 335 } 336 else 337 { 338 return rCandidate; 339 } 340 } 341 342 B2DPolyPolygon createAreaGeometryForEdge( 343 const B2DCubicBezier& rEdge, 344 double fHalfLineWidth, 345 bool bStartRound, 346 bool bEndRound, 347 bool bStartSquare, 348 bool bEndSquare) 349 { 350 // create polygon for edge 351 // Unfortunately, while it would be geometrically correct to not add 352 // the in-between points EdgeEnd and EdgeStart, it leads to rounding 353 // errors when converting to integer polygon coordinates for painting 354 if(rEdge.isBezier()) 355 { 356 // prepare target and data common for upper and lower 357 B2DPolyPolygon aRetval; 358 B2DPolygon aBezierPolygon; 359 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint()); 360 const double fEdgeLength(aPureEdgeVector.getLength()); 361 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength)); 362 B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize(); 363 B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize(); 364 const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA)); 365 const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB)); 366 367 // create upper displacement vectors and check if they cut 368 const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth); 369 const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth); 370 double fCutA(0.0); 371 const tools::CutFlagValue aCutA(tools::findCut( 372 rEdge.getStartPoint(), aPerpendStartA, 373 rEdge.getEndPoint(), aPerpendEndA, 374 CUTFLAG_ALL, &fCutA)); 375 const bool bCutA(CUTFLAG_NONE != aCutA); 376 377 // create lower displacement vectors and check if they cut 378 const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth); 379 const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth); 380 double fCutB(0.0); 381 const tools::CutFlagValue aCutB(tools::findCut( 382 rEdge.getEndPoint(), aPerpendEndB, 383 rEdge.getStartPoint(), aPerpendStartB, 384 CUTFLAG_ALL, &fCutB)); 385 const bool bCutB(CUTFLAG_NONE != aCutB); 386 387 // check if cut happens 388 const bool bCut(bCutA || bCutB); 389 390 // create left edge 391 if(bStartRound || bStartSquare) 392 { 393 basegfx::B2DPolygon aStartPolygon; 394 395 if(bStartRound) 396 { 397 aStartPolygon = tools::createHalfUnitCircle(); 398 aStartPolygon.transform( 399 tools::createScaleShearXRotateTranslateB2DHomMatrix( 400 fHalfLineWidth, fHalfLineWidth, 401 0.0, 402 atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2, 403 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY())); 404 } 405 else // bStartSquare 406 { 407 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth)); 408 409 if(bCut) 410 { 411 aStartPolygon.append(rEdge.getStartPoint() + aPerpendStartB); 412 } 413 414 aStartPolygon.append(aStart + aPerpendStartB); 415 aStartPolygon.append(aStart + aPerpendStartA); 416 417 if(bCut) 418 { 419 aStartPolygon.append(rEdge.getStartPoint() + aPerpendStartA); 420 } 421 } 422 423 if(bCut) 424 { 425 aStartPolygon.append(rEdge.getStartPoint()); 426 aStartPolygon.setClosed(true); 427 aRetval.append(aStartPolygon); 428 } 429 else 430 { 431 aBezierPolygon.append(aStartPolygon); 432 } 433 } 434 else 435 { 436 // append original in-between point 437 aBezierPolygon.append(rEdge.getStartPoint()); 438 } 439 440 // create upper edge. 441 { 442 if(bCutA) 443 { 444 // calculate cut point and add 445 const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStartA * fCutA)); 446 aBezierPolygon.append(aCutPoint); 447 } 448 else 449 { 450 // create scaled bezier segment 451 const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA); 452 const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA); 453 const B2DVector aEdge(aEnd - aStart); 454 const double fLength(aEdge.getLength()); 455 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength); 456 const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint()); 457 const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint()); 458 459 aBezierPolygon.append(aStart); 460 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd); 461 } 462 } 463 464 // create right edge 465 if(bEndRound || bEndSquare) 466 { 467 basegfx::B2DPolygon aEndPolygon; 468 469 if(bEndRound) 470 { 471 aEndPolygon = tools::createHalfUnitCircle(); 472 aEndPolygon.transform( 473 tools::createScaleShearXRotateTranslateB2DHomMatrix( 474 fHalfLineWidth, fHalfLineWidth, 475 0.0, 476 atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2, 477 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY())); 478 } 479 else // bEndSquare 480 { 481 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth)); 482 483 if(bCut) 484 { 485 aEndPolygon.append(rEdge.getEndPoint() + aPerpendEndA); 486 } 487 488 aEndPolygon.append(aEnd + aPerpendEndA); 489 aEndPolygon.append(aEnd + aPerpendEndB); 490 491 if(bCut) 492 { 493 aEndPolygon.append(rEdge.getEndPoint() + aPerpendEndB); 494 } 495 } 496 497 if(bCut) 498 { 499 aEndPolygon.append(rEdge.getEndPoint()); 500 aEndPolygon.setClosed(true); 501 aRetval.append(aEndPolygon); 502 } 503 else 504 { 505 aBezierPolygon.append(aEndPolygon); 506 } 507 } 508 else 509 { 510 // append original in-between point 511 aBezierPolygon.append(rEdge.getEndPoint()); 512 } 513 514 // create lower edge. 515 { 516 if(bCutB) 517 { 518 // calculate cut point and add 519 const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEndB * fCutB)); 520 aBezierPolygon.append(aCutPoint); 521 } 522 else 523 { 524 // create scaled bezier segment 525 const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB); 526 const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB); 527 const B2DVector aEdge(aEnd - aStart); 528 const double fLength(aEdge.getLength()); 529 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength); 530 const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint()); 531 const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint()); 532 533 aBezierPolygon.append(aStart); 534 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd); 535 } 536 } 537 538 // close and return 539 aBezierPolygon.setClosed(true); 540 aRetval.append(aBezierPolygon); 541 542 return aRetval; 543 } 544 else 545 { 546 // Get start and end point, create tangent and set to needed length 547 B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint()); 548 aTangent.setLength(fHalfLineWidth); 549 550 // prepare return value 551 B2DPolygon aEdgePolygon; 552 553 // buffered angle 554 double fAngle(0.0); 555 bool bAngle(false); 556 557 // buffered perpendicular 558 B2DVector aPerpend; 559 bool bPerpend(false); 560 561 // create left vertical 562 if(bStartRound) 563 { 564 aEdgePolygon = tools::createHalfUnitCircle(); 565 fAngle = atan2(aTangent.getY(), aTangent.getX()); 566 bAngle = true; 567 aEdgePolygon.transform( 568 tools::createScaleShearXRotateTranslateB2DHomMatrix( 569 fHalfLineWidth, fHalfLineWidth, 570 0.0, 571 fAngle + F_PI2, 572 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY())); 573 } 574 else 575 { 576 aPerpend.setX(-aTangent.getY()); 577 aPerpend.setY(aTangent.getX()); 578 bPerpend = true; 579 580 if(bStartSquare) 581 { 582 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent); 583 584 aEdgePolygon.append(aStart + aPerpend); 585 aEdgePolygon.append(aStart - aPerpend); 586 } 587 else 588 { 589 aEdgePolygon.append(rEdge.getStartPoint() + aPerpend); 590 aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons 591 aEdgePolygon.append(rEdge.getStartPoint() - aPerpend); 592 } 593 } 594 595 // create right vertical 596 if(bEndRound) 597 { 598 basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle()); 599 600 if(!bAngle) 601 { 602 fAngle = atan2(aTangent.getY(), aTangent.getX()); 603 } 604 605 aEndPolygon.transform( 606 tools::createScaleShearXRotateTranslateB2DHomMatrix( 607 fHalfLineWidth, fHalfLineWidth, 608 0.0, 609 fAngle - F_PI2, 610 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY())); 611 aEdgePolygon.append(aEndPolygon); 612 } 613 else 614 { 615 if(!bPerpend) 616 { 617 aPerpend.setX(-aTangent.getY()); 618 aPerpend.setY(aTangent.getX()); 619 } 620 621 if(bEndSquare) 622 { 623 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent); 624 625 aEdgePolygon.append(aEnd - aPerpend); 626 aEdgePolygon.append(aEnd + aPerpend); 627 } 628 else 629 { 630 aEdgePolygon.append(rEdge.getEndPoint() - aPerpend); 631 aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons 632 aEdgePolygon.append(rEdge.getEndPoint() + aPerpend); 633 } 634 } 635 636 // close and return 637 aEdgePolygon.setClosed(true); 638 639 return B2DPolyPolygon(aEdgePolygon); 640 } 641 } 642 643 B2DPolygon createAreaGeometryForJoin( 644 const B2DVector& rTangentPrev, 645 const B2DVector& rTangentEdge, 646 const B2DVector& rPerpendPrev, 647 const B2DVector& rPerpendEdge, 648 const B2DPoint& rPoint, 649 double fHalfLineWidth, 650 B2DLineJoin eJoin, 651 double fMiterMinimumAngle) 652 { 653 OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)"); 654 OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)"); 655 656 // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint 657 B2DPolygon aEdgePolygon; 658 const B2DPoint aStartPoint(rPoint + rPerpendPrev); 659 const B2DPoint aEndPoint(rPoint + rPerpendEdge); 660 661 // test if for Miter, the angle is too small and the fallback 662 // to bevel needs to be used 663 if(B2DLINEJOIN_MITER == eJoin) 664 { 665 const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge))); 666 667 if((F_PI - fAngle) < fMiterMinimumAngle) 668 { 669 // fallback to bevel 670 eJoin = B2DLINEJOIN_BEVEL; 671 } 672 } 673 674 switch(eJoin) 675 { 676 case B2DLINEJOIN_MITER : 677 { 678 aEdgePolygon.append(aEndPoint); 679 aEdgePolygon.append(rPoint); 680 aEdgePolygon.append(aStartPoint); 681 682 // Look for the cut point between start point along rTangentPrev and 683 // end point along rTangentEdge. -rTangentEdge should be used, but since 684 // the cut value is used for interpolating along the first edge, the negation 685 // is not needed since the same fCut will be found on the first edge. 686 // If it exists, insert it to complete the mitered fill polygon. 687 double fCutPos(0.0); 688 tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos); 689 690 if(0.0 != fCutPos) 691 { 692 const B2DPoint aCutPoint(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos)); 693 aEdgePolygon.append(aCutPoint); 694 } 695 696 break; 697 } 698 case B2DLINEJOIN_ROUND : 699 { 700 // use tooling to add needed EllipseSegment 701 double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX())); 702 double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX())); 703 704 // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI] 705 if(fAngleStart < 0.0) 706 { 707 fAngleStart += F_2PI; 708 } 709 710 if(fAngleEnd < 0.0) 711 { 712 fAngleEnd += F_2PI; 713 } 714 715 const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd)); 716 717 if(aBow.count() > 1) 718 { 719 // #i101491# 720 // use the original start/end positions; the ones from bow creation may be numerically 721 // different due to their different creation. To guarantee good merging quality with edges 722 // and edge roundings (and to reduce point count) 723 aEdgePolygon = aBow; 724 aEdgePolygon.setB2DPoint(0, aStartPoint); 725 aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint); 726 aEdgePolygon.append(rPoint); 727 728 break; 729 } 730 else 731 { 732 // wanted fall-through to default 733 } 734 } 735 default: // B2DLINEJOIN_BEVEL 736 { 737 aEdgePolygon.append(aEndPoint); 738 aEdgePolygon.append(rPoint); 739 aEdgePolygon.append(aStartPoint); 740 741 break; 742 } 743 } 744 745 // create last polygon part for edge 746 aEdgePolygon.setClosed(true); 747 748 return aEdgePolygon; 749 } 750 } // end of anonymus namespace 751 752 namespace tools 753 { 754 B2DPolyPolygon createAreaGeometry( 755 const B2DPolygon& rCandidate, 756 double fHalfLineWidth, 757 B2DLineJoin eJoin, 758 com::sun::star::drawing::LineCap eCap, 759 double fMaxAllowedAngle, 760 double fMaxPartOfEdge, 761 double fMiterMinimumAngle) 762 { 763 if(fMaxAllowedAngle > F_PI2) 764 { 765 fMaxAllowedAngle = F_PI2; 766 } 767 else if(fMaxAllowedAngle < 0.01 * F_PI2) 768 { 769 fMaxAllowedAngle = 0.01 * F_PI2; 770 } 771 772 if(fMaxPartOfEdge > 1.0) 773 { 774 fMaxPartOfEdge = 1.0; 775 } 776 else if(fMaxPartOfEdge < 0.01) 777 { 778 fMaxPartOfEdge = 0.01; 779 } 780 781 if(fMiterMinimumAngle > F_PI) 782 { 783 fMiterMinimumAngle = F_PI; 784 } 785 else if(fMiterMinimumAngle < 0.01 * F_PI) 786 { 787 fMiterMinimumAngle = 0.01 * F_PI; 788 } 789 790 B2DPolygon aCandidate(rCandidate); 791 const double fMaxCos(cos(fMaxAllowedAngle)); 792 793 aCandidate.removeDoublePoints(); 794 aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge); 795 796 const sal_uInt32 nPointCount(aCandidate.count()); 797 798 if(nPointCount) 799 { 800 B2DPolyPolygon aRetval; 801 const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin); 802 const bool bIsClosed(aCandidate.isClosed()); 803 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1); 804 const bool bLineCap(!bIsClosed && com::sun::star::drawing::LineCap_BUTT != eCap); 805 806 if(nEdgeCount) 807 { 808 B2DCubicBezier aEdge; 809 B2DCubicBezier aPrev; 810 811 // prepare edge 812 aEdge.setStartPoint(aCandidate.getB2DPoint(0)); 813 814 if(bIsClosed && bEventuallyCreateLineJoin) 815 { 816 // prepare previous edge 817 const sal_uInt32 nPrevIndex(nPointCount - 1); 818 aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex)); 819 aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex)); 820 aPrev.setControlPointB(aCandidate.getPrevControlPoint(0)); 821 aPrev.setEndPoint(aEdge.getStartPoint()); 822 } 823 824 for(sal_uInt32 a(0); a < nEdgeCount; a++) 825 { 826 // fill current Edge 827 const sal_uInt32 nNextIndex((a + 1) % nPointCount); 828 aEdge.setControlPointA(aCandidate.getNextControlPoint(a)); 829 aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex)); 830 aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex)); 831 832 // check and create linejoin 833 if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a)) 834 { 835 B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize(); 836 B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize(); 837 B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge)); 838 839 if(ORIENTATION_NEUTRAL == aOrientation) 840 { 841 // they are parallell or empty; if they are both not zero and point 842 // in opposite direction, a half-circle is needed 843 if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero()) 844 { 845 const double fAngle(fabs(aTangentPrev.angle(aTangentEdge))); 846 847 if(fTools::equal(fAngle, F_PI)) 848 { 849 // for half-circle production, fallback to positive 850 // orientation 851 aOrientation = ORIENTATION_POSITIVE; 852 } 853 } 854 } 855 856 if(ORIENTATION_POSITIVE == aOrientation) 857 { 858 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth); 859 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth); 860 861 aRetval.append( 862 createAreaGeometryForJoin( 863 aTangentPrev, 864 aTangentEdge, 865 aPerpendPrev, 866 aPerpendEdge, 867 aEdge.getStartPoint(), 868 fHalfLineWidth, 869 eJoin, 870 fMiterMinimumAngle)); 871 } 872 else if(ORIENTATION_NEGATIVE == aOrientation) 873 { 874 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth); 875 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth); 876 877 aRetval.append( 878 createAreaGeometryForJoin( 879 aTangentEdge, 880 aTangentPrev, 881 aPerpendEdge, 882 aPerpendPrev, 883 aEdge.getStartPoint(), 884 fHalfLineWidth, 885 eJoin, 886 fMiterMinimumAngle)); 887 } 888 } 889 890 // create geometry for edge 891 const bool bLast(a + 1 == nEdgeCount); 892 893 if(bLineCap) 894 { 895 const bool bFirst(!a); 896 897 aRetval.append( 898 createAreaGeometryForEdge( 899 aEdge, 900 fHalfLineWidth, 901 bFirst && com::sun::star::drawing::LineCap_ROUND == eCap, 902 bLast && com::sun::star::drawing::LineCap_ROUND == eCap, 903 bFirst && com::sun::star::drawing::LineCap_SQUARE == eCap, 904 bLast && com::sun::star::drawing::LineCap_SQUARE == eCap)); 905 } 906 else 907 { 908 aRetval.append( 909 createAreaGeometryForEdge( 910 aEdge, 911 fHalfLineWidth, 912 false, 913 false, 914 false, 915 false)); 916 } 917 918 // prepare next step 919 if(!bLast) 920 { 921 if(bEventuallyCreateLineJoin) 922 { 923 aPrev = aEdge; 924 } 925 926 aEdge.setStartPoint(aEdge.getEndPoint()); 927 } 928 } 929 } 930 931 return aRetval; 932 } 933 else 934 { 935 return B2DPolyPolygon(rCandidate); 936 } 937 } 938 } // end of namespace tools 939 } // end of namespace basegfx 940 941 ////////////////////////////////////////////////////////////////////////////// 942 // eof 943