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

Generated by: LCOV version 1.10