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