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 <basegfx/polygon/b2dtrapezoid.hxx> 27 #include <basegfx/range/b1drange.hxx> 28 #include <basegfx/polygon/b2dpolygontools.hxx> 29 #include <list> 30 31 ////////////////////////////////////////////////////////////////////////////// 32 33 namespace basegfx 34 { 35 namespace trapezoidhelper 36 { 37 ////////////////////////////////////////////////////////////////////////////// 38 // helper class to hold a simple edge. This is only used for horizontal edges 39 // currently, thus the YPositions will be equal. I did not create a special 40 // class for this since holding the pointers is more effective and also can be 41 // used as baseclass for the traversing edges 42 43 class TrDeSimpleEdge 44 { 45 protected: 46 // pointers to start and end point 47 const B2DPoint* mpStart; 48 const B2DPoint* mpEnd; 49 50 public: 51 // constructor TrDeSimpleEdge(const B2DPoint * pStart,const B2DPoint * pEnd)52 TrDeSimpleEdge( 53 const B2DPoint* pStart, 54 const B2DPoint* pEnd) 55 : mpStart(pStart), 56 mpEnd(pEnd) 57 { 58 } 59 60 // data read access getStart() const61 const B2DPoint& getStart() const { return *mpStart; } getEnd() const62 const B2DPoint& getEnd() const { return *mpEnd; } 63 }; 64 65 ////////////////////////////////////////////////////////////////////////////// 66 // define vector of simple edges 67 68 typedef ::std::vector< TrDeSimpleEdge > TrDeSimpleEdges; 69 70 ////////////////////////////////////////////////////////////////////////////// 71 // helper class for holding a traversing edge. It will always have some 72 // distance in YPos. The slope (in a numerically useful form, see comments) is 73 // hold and used in SortValue to allow sorting traversing edges by Y, X and slope 74 // (in that order) 75 76 class TrDeEdgeEntry : public TrDeSimpleEdge 77 { 78 private: 79 // the slope in a numerical useful form for sorting 80 sal_uInt32 mnSortValue; 81 82 public: 83 // convenience data read access getDeltaX() const84 double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); } getDeltaY() const85 double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); } 86 87 // convenience data read access. SortValue is created on demand since 88 // it is not always used getSortValue() const89 sal_uInt32 getSortValue() const 90 { 91 if(0 != mnSortValue) 92 return mnSortValue; 93 94 // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full 95 // sal_uInt32 range for maximum precision 96 const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / F_PI)); 97 98 // convert to sal_uInt32 value 99 const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant); 100 101 return mnSortValue; 102 } 103 104 // constructor. SortValue can be given when known, use zero otherwise TrDeEdgeEntry(const B2DPoint * pStart,const B2DPoint * pEnd,sal_uInt32 nSortValue=0)105 TrDeEdgeEntry( 106 const B2DPoint* pStart, 107 const B2DPoint* pEnd, 108 sal_uInt32 nSortValue = 0) 109 : TrDeSimpleEdge(pStart, pEnd), 110 mnSortValue(nSortValue) 111 { 112 // force traversal of deltaY downward 113 if(mpEnd->getY() < mpStart->getY()) 114 { 115 std::swap(mpStart, mpEnd); 116 } 117 118 // no horizontal edges allowed, all need to traverse vertically 119 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); 120 } 121 122 // data write access to StartPoint setStart(const B2DPoint * pNewStart)123 void setStart( const B2DPoint* pNewStart) 124 { 125 OSL_ENSURE(0 != pNewStart, "No null pointer allowed here (!)"); 126 127 if(mpStart != pNewStart) 128 { 129 mpStart = pNewStart; 130 131 // no horizontal edges allowed, all need to traverse vertically 132 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); 133 } 134 } 135 136 // data write access to EndPoint setEnd(const B2DPoint * pNewEnd)137 void setEnd( const B2DPoint* pNewEnd) 138 { 139 OSL_ENSURE(0 != pNewEnd, "No null pointer allowed here (!)"); 140 141 if(mpEnd != pNewEnd) 142 { 143 mpEnd = pNewEnd; 144 145 // no horizontal edges allowed, all need to traverse vertically 146 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); 147 } 148 } 149 150 // operator for sort support. Sort by Y, X and slope (in that order) operator <(const TrDeEdgeEntry & rComp) const151 bool operator<(const TrDeEdgeEntry& rComp) const 152 { 153 if(fTools::equal(getStart().getY(), rComp.getStart().getY(), fTools::getSmallValue())) 154 { 155 if(fTools::equal(getStart().getX(), rComp.getStart().getX(), fTools::getSmallValue())) 156 { 157 // when start points are equal, use the direction the edge is pointing 158 // to. That value is created on demand and derived from atan2 in the 159 // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this 160 // class) and scaled to sal_uInt32 range for best precision. 0 means no angle, 161 // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left 162 // the edge traverses. 163 return (getSortValue() > rComp.getSortValue()); 164 } 165 else 166 { 167 return fTools::less(getStart().getX(), rComp.getStart().getX()); 168 } 169 } 170 else 171 { 172 return fTools::less(getStart().getY(), rComp.getStart().getY()); 173 } 174 } 175 176 // method for cut support getCutPointForGivenY(double fGivenY)177 B2DPoint getCutPointForGivenY(double fGivenY) 178 { 179 // Calculate cut point locally (do not use interpolate) since it is numerically 180 // necessary to guarantee the new, equal Y-coordinate 181 const double fFactor((fGivenY - getStart().getY()) / getDeltaY()); 182 const double fDeltaXNew(fFactor * getDeltaX()); 183 184 return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY); 185 } 186 }; 187 188 ////////////////////////////////////////////////////////////////////////////// 189 // define double linked list of edges (for fast random insert) 190 191 typedef ::std::list< TrDeEdgeEntry > TrDeEdgeEntries; 192 193 } // end of anonymous namespace 194 } // end of namespace basegfx 195 196 ////////////////////////////////////////////////////////////////////////////// 197 198 namespace basegfx 199 { 200 namespace trapezoidhelper 201 { 202 // helper class to handle the complete trapezoid subdivision of a PolyPolygon 203 class TrapezoidSubdivider 204 { 205 private: 206 // local data 207 sal_uInt32 mnInitialEdgeEntryCount; 208 TrDeEdgeEntries maTrDeEdgeEntries; 209 ::std::vector< B2DPoint > maPoints; 210 ::std::vector< B2DPoint* > maNewPoints; 211 addEdgeSorted(TrDeEdgeEntries::iterator aCurrent,const TrDeEdgeEntry & rNewEdge)212 void addEdgeSorted( 213 TrDeEdgeEntries::iterator aCurrent, 214 const TrDeEdgeEntry& rNewEdge) 215 { 216 // Loop while new entry is bigger, use operator< 217 while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge) 218 { 219 aCurrent++; 220 } 221 222 // Insert before first which is smaller or equal or at end 223 maTrDeEdgeEntries.insert(aCurrent, rNewEdge); 224 } 225 splitEdgeAtGivenPoint(TrDeEdgeEntries::reference aEdge,const B2DPoint & rCutPoint,TrDeEdgeEntries::iterator aCurrent)226 bool splitEdgeAtGivenPoint( 227 TrDeEdgeEntries::reference aEdge, 228 const B2DPoint& rCutPoint, 229 TrDeEdgeEntries::iterator aCurrent) 230 { 231 // do not create edges without deltaY: do not split when start is identical 232 if(aEdge.getStart().equal(rCutPoint, fTools::getSmallValue())) 233 { 234 return false; 235 } 236 237 // do not create edges without deltaY: do not split when end is identical 238 if(aEdge.getEnd().equal(rCutPoint, fTools::getSmallValue())) 239 { 240 return false; 241 } 242 243 const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY()); 244 245 if(fTools::lessOrEqual(fOldDeltaYStart, 0.0)) 246 { 247 // do not split: the resulting edge would be horizontal 248 // correct it to new start point 249 aEdge.setStart(&rCutPoint); 250 return false; 251 } 252 253 const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY()); 254 255 if(fTools::lessOrEqual(fNewDeltaYStart, 0.0)) 256 { 257 // do not split: the resulting edge would be horizontal 258 // correct it to new end point 259 aEdge.setEnd(&rCutPoint); 260 return false; 261 } 262 263 // Create new entry 264 const TrDeEdgeEntry aNewEdge( 265 &rCutPoint, 266 &aEdge.getEnd(), 267 aEdge.getSortValue()); 268 269 // Correct old entry 270 aEdge.setEnd(&rCutPoint); 271 272 // Insert sorted (to avoid new sort) 273 addEdgeSorted(aCurrent, aNewEdge); 274 275 return true; 276 } 277 testAndCorrectEdgeIntersection(TrDeEdgeEntries::reference aEdgeA,TrDeEdgeEntries::reference aEdgeB,TrDeEdgeEntries::iterator aCurrent)278 bool testAndCorrectEdgeIntersection( 279 TrDeEdgeEntries::reference aEdgeA, 280 TrDeEdgeEntries::reference aEdgeB, 281 TrDeEdgeEntries::iterator aCurrent) 282 { 283 // Exclude simple cases: same start or end point 284 if(aEdgeA.getStart().equal(aEdgeB.getStart(), fTools::getSmallValue())) 285 { 286 return false; 287 } 288 289 if(aEdgeA.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) 290 { 291 return false; 292 } 293 294 if(aEdgeA.getEnd().equal(aEdgeB.getStart(), fTools::getSmallValue())) 295 { 296 return false; 297 } 298 299 if(aEdgeA.getEnd().equal(aEdgeB.getEnd(), fTools::getSmallValue())) 300 { 301 return false; 302 } 303 304 // Exclude simple cases: one of the edges has no length anymore 305 if(aEdgeA.getStart().equal(aEdgeA.getEnd(), fTools::getSmallValue())) 306 { 307 return false; 308 } 309 310 if(aEdgeB.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) 311 { 312 return false; 313 } 314 315 // check if one point is on the other edge (a touch, not a cut) 316 const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY()); 317 318 if(tools::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB)) 319 { 320 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent); 321 } 322 323 if(tools::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB)) 324 { 325 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent); 326 } 327 328 const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY()); 329 330 if(tools::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA)) 331 { 332 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent); 333 } 334 335 if(tools::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA)) 336 { 337 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent); 338 } 339 340 // check for cut inside edges. Use both t-values to choose the more precise 341 // one later 342 double fCutA(0.0); 343 double fCutB(0.0); 344 345 if(tools::findCut( 346 aEdgeA.getStart(), aDeltaA, 347 aEdgeB.getStart(), aDeltaB, 348 CUTFLAG_LINE, 349 &fCutA, 350 &fCutB)) 351 { 352 // use a simple metric (length criteria) for choosing the numerically 353 // better cut 354 const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY()); 355 const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY()); 356 const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB); 357 B2DPoint* pNewPoint = bAIsLonger 358 ? new B2DPoint(aEdgeA.getStart() + (fCutA * aDeltaA)) 359 : new B2DPoint(aEdgeB.getStart() + (fCutB * aDeltaB)); 360 bool bRetval(false); 361 362 // try to split both edges 363 bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent); 364 bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent); 365 366 if(bRetval) 367 { 368 maNewPoints.push_back(pNewPoint); 369 } 370 else 371 { 372 delete pNewPoint; 373 } 374 375 return bRetval; 376 } 377 378 return false; 379 } 380 solveHorizontalEdges(TrDeSimpleEdges & rTrDeSimpleEdges)381 void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges) 382 { 383 if(rTrDeSimpleEdges.size() && maTrDeEdgeEntries.size()) 384 { 385 // there were horizontal edges. These can be excluded, but 386 // cuts with other edges need to be solved and added before 387 // ignoring them 388 sal_uInt32 a(0); 389 390 for(a = 0; a < rTrDeSimpleEdges.size(); a++) 391 { 392 // get horizontal edge as candidate; prepare its range and fixed Y 393 const TrDeSimpleEdge& rHorEdge = rTrDeSimpleEdges[a]; 394 const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX()); 395 const double fFixedY(rHorEdge.getStart().getY()); 396 397 // loop over traversing edges 398 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); 399 400 do 401 { 402 // get compare edge 403 TrDeEdgeEntries::reference aCompare(*aCurrent++); 404 405 if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY)) 406 { 407 // edge ends above horizontal edge, continue 408 continue; 409 } 410 411 if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY)) 412 { 413 // edge starts below horizontal edge, continue 414 continue; 415 } 416 417 // vertical overlap, get horizontal range 418 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); 419 420 if(aRange.overlaps(aCompareRange)) 421 { 422 // possible cut, get cut point 423 const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY)); 424 425 if(fTools::more(aSplit.getX(), aRange.getMinimum()) 426 && fTools::less(aSplit.getX(), aRange.getMaximum())) 427 { 428 // cut is in XRange of horizontal edge, potentially needed cut 429 B2DPoint* pNewPoint = new B2DPoint(aSplit); 430 431 if(splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent)) 432 { 433 maNewPoints.push_back(pNewPoint); 434 } 435 else 436 { 437 delete pNewPoint; 438 } 439 } 440 } 441 } 442 while(aCurrent != maTrDeEdgeEntries.end() 443 && fTools::less(aCurrent->getStart().getY(), fFixedY)); 444 } 445 } 446 } 447 448 public: TrapezoidSubdivider(const B2DPolyPolygon & rSourcePolyPolygon)449 TrapezoidSubdivider( 450 const B2DPolyPolygon& rSourcePolyPolygon) 451 : mnInitialEdgeEntryCount(0), 452 maTrDeEdgeEntries(), 453 maPoints(), 454 maNewPoints() 455 { 456 B2DPolyPolygon aSource(rSourcePolyPolygon); 457 const sal_uInt32 nPolygonCount(rSourcePolyPolygon.count()); 458 TrDeSimpleEdges aTrDeSimpleEdges; 459 sal_uInt32 a(0), b(0); 460 sal_uInt32 nAllPointCount(0); 461 462 // ensure there are no curves used 463 if(aSource.areControlPointsUsed()) 464 { 465 aSource = aSource.getDefaultAdaptiveSubdivision(); 466 } 467 468 for(a = 0; a < nPolygonCount; a++) 469 { 470 // 1st run: count points 471 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); 472 const sal_uInt32 nCount(aPolygonCandidate.count()); 473 474 if(nCount > 2) 475 { 476 nAllPointCount += nCount; 477 } 478 } 479 480 if(nAllPointCount) 481 { 482 // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore 483 // after 2nd loop since pointers to it are used in the edges 484 maPoints.reserve(nAllPointCount); 485 486 for(a = 0; a < nPolygonCount; a++) 487 { 488 // 2nd run: add points 489 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); 490 const sal_uInt32 nCount(aPolygonCandidate.count()); 491 492 if(nCount > 2) 493 { 494 for(b = 0; b < nCount; b++) 495 { 496 maPoints.push_back(aPolygonCandidate.getB2DPoint(b)); 497 } 498 } 499 } 500 501 // Moved the edge construction to a 3rd run: doing it in the 2nd run is 502 // possible (and i used it), but requires a working vector::reserve() 503 // implementation, else the vector will be reallocated and the pointers 504 // in the edges may be wrong. Security first here. 505 sal_uInt32 nStartIndex(0); 506 507 for(a = 0; a < nPolygonCount; a++) 508 { 509 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); 510 const sal_uInt32 nCount(aPolygonCandidate.count()); 511 512 if(nCount > 2) 513 { 514 // get the last point of the current polygon 515 B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]); 516 517 for(b = 0; b < nCount; b++) 518 { 519 // get next point 520 B2DPoint* pCurr(&maPoints[nStartIndex++]); 521 522 if(fTools::equal(pPrev->getY(), pCurr->getY(), fTools::getSmallValue())) 523 { 524 // horizontal edge, check for single point 525 if(!fTools::equal(pPrev->getX(), pCurr->getX(), fTools::getSmallValue())) 526 { 527 // X-order not needed, just add 528 aTrDeSimpleEdges.push_back(TrDeSimpleEdge(pPrev, pCurr)); 529 530 const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5); 531 pPrev->setY(fMiddle); 532 pCurr->setY(fMiddle); 533 } 534 } 535 else 536 { 537 // vertical edge. Positive Y-direction is guaranteed by the 538 // TrDeEdgeEntry constructor 539 maTrDeEdgeEntries.push_back(TrDeEdgeEntry(pPrev, pCurr, 0)); 540 mnInitialEdgeEntryCount++; 541 } 542 543 // prepare next step 544 pPrev = pCurr; 545 } 546 } 547 } 548 } 549 550 if(maTrDeEdgeEntries.size()) 551 { 552 // single and initial sort of traversing edges 553 maTrDeEdgeEntries.sort(); 554 555 // solve horizontal edges if there are any detected 556 solveHorizontalEdges(aTrDeSimpleEdges); 557 } 558 } 559 ~TrapezoidSubdivider()560 ~TrapezoidSubdivider() 561 { 562 // delete the extra points created for cuts 563 const sal_uInt32 nCount(maNewPoints.size()); 564 565 for(sal_uInt32 a(0); a < nCount; a++) 566 { 567 delete maNewPoints[a]; 568 } 569 } 570 Subdivide(B2DTrapezoidVector & ro_Result)571 void Subdivide(B2DTrapezoidVector& ro_Result) 572 { 573 // This is the central subdivider. The strategy is to use the first two entries 574 // from the traversing edges as a potential trapezoid and do the needed corrections 575 // and adaptions on the way. 576 // 577 // There always must be two edges with the same YStart value: When adding the polygons 578 // in the constructor, there is always a topmost point from which two edges start; when 579 // the topmost is an edge, there is a start and end of this edge from which two edges 580 // start. All cases have two edges with same StartY (QED). 581 // 582 // Based on this these edges get corrected when: 583 // - one is longer than the other 584 // - they intersect 585 // - they intersect with other edges 586 // - another edge starts inside the thought trapezoid 587 // 588 // All this cases again produce a valid state so that the first two edges have a common 589 // Ystart again. Some cases lead to a restart of the process, some allow consuming the 590 // edges and create the intended trapezoid. 591 // 592 // Be careful when doing changes here: It is essential to keep all possible paths 593 // in valid states and to be numerically correct. This is especially needed e.g. 594 // by using fTools::equal(..) in the more robust small-value incarnation. 595 B1DRange aLeftRange; 596 B1DRange aRightRange; 597 598 if(!maTrDeEdgeEntries.empty()) 599 { 600 // measuring shows that the relation between edges and created trapezoids is 601 // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do 602 // not use maTrDeEdgeEntries.size() since that may be a non-constant time 603 // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain 604 // the roughly counted adds to the List 605 ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount); 606 } 607 608 while(!maTrDeEdgeEntries.empty()) 609 { 610 // Prepare current operator and get first edge 611 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); 612 TrDeEdgeEntries::reference aLeft(*aCurrent++); 613 614 if(aCurrent == maTrDeEdgeEntries.end()) 615 { 616 // Should not happen: No 2nd edge; consume the single edge 617 // to not have an endless loop and start next. During development 618 // i constantly had breakpoints here, so i am sure enough to add an 619 // assertion here 620 OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); 621 maTrDeEdgeEntries.pop_front(); 622 continue; 623 } 624 625 // get second edge 626 TrDeEdgeEntries::reference aRight(*aCurrent++); 627 628 if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY(), fTools::getSmallValue())) 629 { 630 // Should not happen: We have a 2nd edge, but YStart is on another 631 // line; consume the single edge to not have an endless loop and start 632 // next. During development i constantly had breakpoints here, so i am 633 // sure enough to add an assertion here 634 OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); 635 maTrDeEdgeEntries.pop_front(); 636 continue; 637 } 638 639 // aLeft and aRight build a thought trapezoid now. They have a common 640 // start line (same Y for start points). Potentially, one of the edges 641 // is longer than the other. It is only needed to look at the shorter 642 // length which build the potential trapezoid. To do so, get the end points 643 // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd 644 // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses. 645 B2DPoint aLeftEnd(aLeft.getEnd()); 646 B2DPoint aRightEnd(aRight.getEnd()); 647 648 // check if end points are on the same line. If yes, no adaption 649 // needs to be prepared. Also remember which one actually is longer. 650 const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY(), fTools::getSmallValue())); 651 bool bLeftIsLonger(false); 652 653 if(!bEndOnSameLine) 654 { 655 // check which edge is longer and correct accordingly 656 bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY()); 657 658 if(bLeftIsLonger) 659 { 660 aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY()); 661 } 662 else 663 { 664 aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY()); 665 } 666 } 667 668 // check for same start and end points 669 const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart(), fTools::getSmallValue())); 670 const bool bSameEndPoint(aLeftEnd.equal(aRightEnd, fTools::getSmallValue())); 671 672 // check the simple case that the edges form a 'blind' edge (deadend) 673 if(bSameStartPoint && bSameEndPoint) 674 { 675 // correct the longer edge if prepared 676 if(!bEndOnSameLine) 677 { 678 if(bLeftIsLonger) 679 { 680 B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); 681 682 if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) 683 { 684 maNewPoints.push_back(pNewPoint); 685 } 686 else 687 { 688 delete pNewPoint; 689 } 690 } 691 else 692 { 693 B2DPoint* pNewPoint = new B2DPoint(aRightEnd); 694 695 if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) 696 { 697 maNewPoints.push_back(pNewPoint); 698 } 699 else 700 { 701 delete pNewPoint; 702 } 703 } 704 } 705 706 // consume both edges and start next run 707 maTrDeEdgeEntries.pop_front(); 708 maTrDeEdgeEntries.pop_front(); 709 710 continue; 711 } 712 713 // check if the edges self-intersect. This can only happen when 714 // start and end point are different 715 bool bRangesSet(false); 716 717 if(!(bSameStartPoint || bSameEndPoint)) 718 { 719 // get XRanges of edges 720 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); 721 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); 722 bRangesSet = true; 723 724 // use fast range test first 725 if(aLeftRange.overlaps(aRightRange)) 726 { 727 // real cut test and correction. If correction was needed, 728 // start new run 729 if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent)) 730 { 731 continue; 732 } 733 } 734 } 735 736 // now we need to check if there are intersections with other edges 737 // or if other edges start inside the candidate trapezoid 738 if(aCurrent != maTrDeEdgeEntries.end() 739 && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY())) 740 { 741 // get XRanges of edges 742 if(!bRangesSet) 743 { 744 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); 745 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); 746 } 747 748 // build full XRange for fast check 749 B1DRange aAllRange(aLeftRange); 750 aAllRange.expand(aRightRange); 751 752 // prepare loop iterator; aCurrent needs to stay unchanged for 753 // eventual sorted insertions of new EdgeNodes. Also prepare stop flag 754 TrDeEdgeEntries::iterator aLoop(aCurrent); 755 bool bDone(false); 756 757 do 758 { 759 // get compare edge and its XRange 760 TrDeEdgeEntries::reference aCompare(*aLoop++); 761 762 // avoid edges using the same start point as one of 763 // the edges. These can neither have their start point 764 // in the thought trapezoid nor cut with one of the edges 765 if(aCompare.getStart().equal(aRight.getStart(), fTools::getSmallValue())) 766 { 767 continue; 768 } 769 770 // get compare XRange 771 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); 772 773 // use fast range test first 774 if(aAllRange.overlaps(aCompareRange)) 775 { 776 // check for start point inside thought trapezoid 777 if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY())) 778 { 779 // calculate the two possible split points at compare's Y 780 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY())); 781 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY())); 782 783 // check for start point of aCompare being inside thought 784 // trapezoid 785 if(aCompare.getStart().getX() >= aSplitLeft.getX() && 786 aCompare.getStart().getX() <= aSplitRight.getX()) 787 { 788 // is inside, correct and restart loop 789 B2DPoint* pNewLeft = new B2DPoint(aSplitLeft); 790 791 if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent)) 792 { 793 maNewPoints.push_back(pNewLeft); 794 bDone = true; 795 } 796 else 797 { 798 delete pNewLeft; 799 } 800 801 B2DPoint* pNewRight = new B2DPoint(aSplitRight); 802 803 if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent)) 804 { 805 maNewPoints.push_back(pNewRight); 806 bDone = true; 807 } 808 else 809 { 810 delete pNewRight; 811 } 812 } 813 } 814 815 if(!bDone && aLeftRange.overlaps(aCompareRange)) 816 { 817 // test for concrete cut of compare edge with left edge 818 bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent); 819 } 820 821 if(!bDone && aRightRange.overlaps(aCompareRange)) 822 { 823 // test for concrete cut of compare edge with Right edge 824 bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent); 825 } 826 } 827 } 828 while(!bDone 829 && aLoop != maTrDeEdgeEntries.end() 830 && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY())); 831 832 if(bDone) 833 { 834 // something needed to be changed; start next loop 835 continue; 836 } 837 } 838 839 // when we get here, the intended trapezoid can be used. It needs to 840 // be corrected, eventually (if prepared); but this is no reason not to 841 // use it in the same loop iteration 842 if(!bEndOnSameLine) 843 { 844 if(bLeftIsLonger) 845 { 846 B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); 847 848 if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) 849 { 850 maNewPoints.push_back(pNewPoint); 851 } 852 else 853 { 854 delete pNewPoint; 855 } 856 } 857 else 858 { 859 B2DPoint* pNewPoint = new B2DPoint(aRightEnd); 860 861 if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) 862 { 863 maNewPoints.push_back(pNewPoint); 864 } 865 else 866 { 867 delete pNewPoint; 868 } 869 } 870 } 871 872 // the two edges start at the same Y, they use the same DeltaY, they 873 // do not cut themselves and not any other edge in range. Create a 874 // B2DTrapezoid and consume both edges 875 ro_Result.push_back( 876 B2DTrapezoid( 877 aLeft.getStart().getX(), 878 aRight.getStart().getX(), 879 aLeft.getStart().getY(), 880 aLeftEnd.getX(), 881 aRightEnd.getX(), 882 aLeftEnd.getY())); 883 884 maTrDeEdgeEntries.pop_front(); 885 maTrDeEdgeEntries.pop_front(); 886 } 887 } 888 }; 889 } // end of anonymous namespace 890 } // end of namespace basegfx 891 892 ////////////////////////////////////////////////////////////////////////////// 893 894 namespace basegfx 895 { B2DTrapezoid(const double & rfTopXLeft,const double & rfTopXRight,const double & rfTopY,const double & rfBottomXLeft,const double & rfBottomXRight,const double & rfBottomY)896 B2DTrapezoid::B2DTrapezoid( 897 const double& rfTopXLeft, 898 const double& rfTopXRight, 899 const double& rfTopY, 900 const double& rfBottomXLeft, 901 const double& rfBottomXRight, 902 const double& rfBottomY) 903 : mfTopXLeft(rfTopXLeft), 904 mfTopXRight(rfTopXRight), 905 mfTopY(rfTopY), 906 mfBottomXLeft(rfBottomXLeft), 907 mfBottomXRight(rfBottomXRight), 908 mfBottomY(rfBottomY) 909 { 910 // guarantee mfTopXRight >= mfTopXLeft 911 if(mfTopXLeft > mfTopXRight) 912 { 913 std::swap(mfTopXLeft, mfTopXRight); 914 } 915 916 // guarantee mfBottomXRight >= mfBottomXLeft 917 if(mfBottomXLeft > mfBottomXRight) 918 { 919 std::swap(mfBottomXLeft, mfBottomXRight); 920 } 921 922 // guarantee mfBottomY >= mfTopY 923 if(mfTopY > mfBottomY) 924 { 925 std::swap(mfTopY, mfBottomY); 926 std::swap(mfTopXLeft, mfBottomXLeft); 927 std::swap(mfTopXRight, mfBottomXRight); 928 } 929 } 930 getB2DPolygon() const931 B2DPolygon B2DTrapezoid::getB2DPolygon() const 932 { 933 B2DPolygon aRetval; 934 935 aRetval.append(B2DPoint(getTopXLeft(), getTopY())); 936 aRetval.append(B2DPoint(getTopXRight(), getTopY())); 937 aRetval.append(B2DPoint(getBottomXRight(), getBottomY())); 938 aRetval.append(B2DPoint(getBottomXLeft(), getBottomY())); 939 aRetval.setClosed(true); 940 941 return aRetval; 942 } 943 } // end of namespace basegfx 944 945 ////////////////////////////////////////////////////////////////////////////// 946 947 namespace basegfx 948 { 949 namespace tools 950 { 951 // convert Source PolyPolygon to trapezoids trapezoidSubdivide(B2DTrapezoidVector & ro_Result,const B2DPolyPolygon & rSourcePolyPolygon)952 void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon) 953 { 954 trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon); 955 956 aTrapezoidSubdivider.Subdivide(ro_Result); 957 } 958 createLineTrapezoidFromEdge(B2DTrapezoidVector & ro_Result,const B2DPoint & rPointA,const B2DPoint & rPointB,double fLineWidth)959 void createLineTrapezoidFromEdge( 960 B2DTrapezoidVector& ro_Result, 961 const B2DPoint& rPointA, 962 const B2DPoint& rPointB, 963 double fLineWidth) 964 { 965 if(fTools::lessOrEqual(fLineWidth, 0.0)) 966 { 967 // no line width 968 return; 969 } 970 971 if(rPointA.equal(rPointB, fTools::getSmallValue())) 972 { 973 // points are equal, no edge 974 return; 975 } 976 977 const double fHalfLineWidth(0.5 * fLineWidth); 978 979 if(fTools::equal(rPointA.getX(), rPointB.getX(), fTools::getSmallValue())) 980 { 981 // vertical line 982 const double fLeftX(rPointA.getX() - fHalfLineWidth); 983 const double fRightX(rPointA.getX() + fHalfLineWidth); 984 985 ro_Result.push_back( 986 B2DTrapezoid( 987 fLeftX, 988 fRightX, 989 std::min(rPointA.getY(), rPointB.getY()), 990 fLeftX, 991 fRightX, 992 std::max(rPointA.getY(), rPointB.getY()))); 993 } 994 else if(fTools::equal(rPointA.getY(), rPointB.getY(), fTools::getSmallValue())) 995 { 996 // horizontal line 997 const double fLeftX(std::min(rPointA.getX(), rPointB.getX())); 998 const double fRightX(std::max(rPointA.getX(), rPointB.getX())); 999 1000 ro_Result.push_back( 1001 B2DTrapezoid( 1002 fLeftX, 1003 fRightX, 1004 rPointA.getY() - fHalfLineWidth, 1005 fLeftX, 1006 fRightX, 1007 rPointA.getY() + fHalfLineWidth)); 1008 } 1009 else 1010 { 1011 // diagonal line 1012 // create perpendicular vector 1013 const B2DVector aDelta(rPointB - rPointA); 1014 B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX()); 1015 aPerpendicular.setLength(fHalfLineWidth); 1016 1017 // create StartLow, StartHigh, EndLow and EndHigh 1018 const B2DPoint aStartLow(rPointA + aPerpendicular); 1019 const B2DPoint aStartHigh(rPointA - aPerpendicular); 1020 const B2DPoint aEndHigh(rPointB - aPerpendicular); 1021 const B2DPoint aEndLow(rPointB + aPerpendicular); 1022 1023 // create EdgeEntries 1024 basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries; 1025 1026 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartLow, &aStartHigh, 0)); 1027 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartHigh, &aEndHigh, 0)); 1028 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndHigh, &aEndLow, 0)); 1029 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndLow, &aStartLow, 0)); 1030 aTrDeEdgeEntries.sort(); 1031 1032 // here we know we have exactly four edges, and they do not cut, touch or 1033 // intersect. This makes processing much easier. Get the first two as start 1034 // edges for the thought trapezoid 1035 basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin()); 1036 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++); 1037 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++); 1038 const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY(), fTools::getSmallValue())); 1039 1040 if(bEndOnSameLine) 1041 { 1042 // create two triangle trapezoids 1043 ro_Result.push_back( 1044 B2DTrapezoid( 1045 aLeft.getStart().getX(), 1046 aRight.getStart().getX(), 1047 aLeft.getStart().getY(), 1048 aLeft.getEnd().getX(), 1049 aRight.getEnd().getX(), 1050 aLeft.getEnd().getY())); 1051 1052 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); 1053 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); 1054 1055 ro_Result.push_back( 1056 B2DTrapezoid( 1057 aLeft2.getStart().getX(), 1058 aRight2.getStart().getX(), 1059 aLeft2.getStart().getY(), 1060 aLeft2.getEnd().getX(), 1061 aRight2.getEnd().getX(), 1062 aLeft2.getEnd().getY())); 1063 } 1064 else 1065 { 1066 // create three trapezoids. Check which edge is longer and 1067 // correct accordingly 1068 const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY())); 1069 1070 if(bLeftIsLonger) 1071 { 1072 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); 1073 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); 1074 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY())); 1075 const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY())); 1076 1077 ro_Result.push_back( 1078 B2DTrapezoid( 1079 aLeft.getStart().getX(), 1080 aRight.getStart().getX(), 1081 aLeft.getStart().getY(), 1082 aSplitLeft.getX(), 1083 aRight.getEnd().getX(), 1084 aRight.getEnd().getY())); 1085 1086 ro_Result.push_back( 1087 B2DTrapezoid( 1088 aSplitLeft.getX(), 1089 aRight.getEnd().getX(), 1090 aRight.getEnd().getY(), 1091 aLeft2.getStart().getX(), 1092 aSplitRight.getX(), 1093 aLeft2.getStart().getY())); 1094 1095 ro_Result.push_back( 1096 B2DTrapezoid( 1097 aLeft2.getStart().getX(), 1098 aSplitRight.getX(), 1099 aLeft2.getStart().getY(), 1100 aLeft2.getEnd().getX(), 1101 aRight2.getEnd().getX(), 1102 aLeft2.getEnd().getY())); 1103 } 1104 else 1105 { 1106 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); 1107 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); 1108 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY())); 1109 const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY())); 1110 1111 ro_Result.push_back( 1112 B2DTrapezoid( 1113 aLeft.getStart().getX(), 1114 aRight.getStart().getX(), 1115 aLeft.getStart().getY(), 1116 aLeft.getEnd().getX(), 1117 aSplitRight.getX(), 1118 aLeft.getEnd().getY())); 1119 1120 ro_Result.push_back( 1121 B2DTrapezoid( 1122 aLeft.getEnd().getX(), 1123 aSplitRight.getX(), 1124 aLeft.getEnd().getY(), 1125 aSplitLeft.getX(), 1126 aRight.getEnd().getX(), 1127 aRight2.getStart().getY())); 1128 1129 ro_Result.push_back( 1130 B2DTrapezoid( 1131 aSplitLeft.getX(), 1132 aRight.getEnd().getX(), 1133 aRight2.getStart().getY(), 1134 aLeft2.getEnd().getX(), 1135 aRight2.getEnd().getX(), 1136 aLeft2.getEnd().getY())); 1137 } 1138 } 1139 } 1140 } 1141 createLineTrapezoidFromB2DPolygon(B2DTrapezoidVector & ro_Result,const B2DPolygon & rPolygon,double fLineWidth)1142 void createLineTrapezoidFromB2DPolygon( 1143 B2DTrapezoidVector& ro_Result, 1144 const B2DPolygon& rPolygon, 1145 double fLineWidth) 1146 { 1147 if(fTools::lessOrEqual(fLineWidth, 0.0)) 1148 { 1149 return; 1150 } 1151 1152 // ensure there are no curves used 1153 B2DPolygon aSource(rPolygon); 1154 1155 if(aSource.areControlPointsUsed()) 1156 { 1157 const double fPrecisionFactor = 0.25; 1158 aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor ); 1159 } 1160 1161 const sal_uInt32 nPointCount(aSource.count()); 1162 1163 if(!nPointCount) 1164 { 1165 return; 1166 } 1167 1168 const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1); 1169 B2DPoint aCurrent(aSource.getB2DPoint(0)); 1170 1171 ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount)); 1172 1173 for(sal_uInt32 a(0); a < nEdgeCount; a++) 1174 { 1175 const sal_uInt32 nNextIndex((a + 1) % nPointCount); 1176 const B2DPoint aNext(aSource.getB2DPoint(nNextIndex)); 1177 1178 createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth); 1179 aCurrent = aNext; 1180 } 1181 } 1182 createLineTrapezoidFromB2DPolyPolygon(B2DTrapezoidVector & ro_Result,const B2DPolyPolygon & rPolyPolygon,double fLineWidth)1183 void createLineTrapezoidFromB2DPolyPolygon( 1184 B2DTrapezoidVector& ro_Result, 1185 const B2DPolyPolygon& rPolyPolygon, 1186 double fLineWidth) 1187 { 1188 if(fTools::lessOrEqual(fLineWidth, 0.0)) 1189 { 1190 return; 1191 } 1192 1193 // ensure there are no curves used 1194 B2DPolyPolygon aSource(rPolyPolygon); 1195 1196 if(aSource.areControlPointsUsed()) 1197 { 1198 aSource = aSource.getDefaultAdaptiveSubdivision(); 1199 } 1200 1201 const sal_uInt32 nCount(aSource.count()); 1202 1203 if(!nCount) 1204 { 1205 return; 1206 } 1207 1208 for(sal_uInt32 a(0); a < nCount; a++) 1209 { 1210 createLineTrapezoidFromB2DPolygon( 1211 ro_Result, 1212 aSource.getB2DPolygon(a), 1213 fLineWidth); 1214 } 1215 } 1216 1217 } // end of namespace tools 1218 } // end of namespace basegfx 1219 1220 ////////////////////////////////////////////////////////////////////////////// 1221 // eof 1222