LCOV - code coverage report
Current view: top level - basegfx/source/polygon - b2dlinegeometry.cxx (source / functions) Hit Total Coverage
Test: commit 10e77ab3ff6f4314137acd6e2702a6e5c1ce1fae Lines: 325 362 89.8 %
Date: 2014-11-03 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.10