LCOV - code coverage report
Current view: top level - basegfx/source/polygon - b2dlinegeometry.cxx (source / functions) Hit Total Coverage
Test: commit c8344322a7af75b84dd3ca8f78b05543a976dfd5 Lines: 325 362 89.8 %
Date: 2015-06-13 12:38:46 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         351 :         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         351 :             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         351 :             if(fWidth < 0.0)
      56             :             {
      57           0 :                 fWidth = -fWidth;
      58             :             }
      59             : 
      60         351 :             if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
      61             :             {
      62         351 :                 if(fDockingPosition < 0.0)
      63             :                 {
      64           0 :                     fDockingPosition = 0.0;
      65             :                 }
      66         351 :                 else if(fDockingPosition > 1.0)
      67             :                 {
      68           0 :                     fDockingPosition = 1.0;
      69             :                 }
      70             : 
      71             :                 // init return value from arrow
      72         351 :                 aRetval.append(rArrow);
      73             : 
      74             :                 // get size of the arrow
      75         351 :                 const B2DRange aArrowSize(getRange(rArrow));
      76             : 
      77             :                 // build ArrowTransform; center in X, align with axis in Y
      78             :                 B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
      79         351 :                     -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
      80             : 
      81             :                 // scale to target size
      82         351 :                 const double fArrowScale(fWidth / (aArrowSize.getWidth()));
      83         351 :                 aArrowTransform.scale(fArrowScale, fArrowScale);
      84             : 
      85             :                 // get arrow size in Y
      86         702 :                 B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
      87         351 :                 aUpperCenter *= aArrowTransform;
      88         351 :                 const double fArrowYLength(B2DVector(aUpperCenter).getLength());
      89             : 
      90             :                 // move arrow to have docking position centered
      91         351 :                 aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition + fShift);
      92             : 
      93             :                 // prepare polygon length
      94         351 :                 if(fTools::equalZero(fCandidateLength))
      95             :                 {
      96          58 :                     fCandidateLength = getLength(rCandidate);
      97             :                 }
      98             : 
      99             :                 // get the polygon vector we want to plant this arrow on
     100         351 :                 const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition) - fShift);
     101         702 :                 const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
     102             :                 const B2DVector aTail(getPositionAbsolute(rCandidate,
     103         702 :                     (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
     104             : 
     105             :                 // from that vector, take the needed rotation and add rotate for arrow to transformation
     106         702 :                 const B2DVector aTargetDirection(aHead - aTail);
     107         351 :                 const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));
     108             : 
     109             :                 // rotate around docking position
     110         351 :                 aArrowTransform.rotate(fRotation);
     111             : 
     112             :                 // move arrow docking position to polygon head
     113         351 :                 aArrowTransform.translate(aHead.getX(), aHead.getY());
     114             : 
     115             :                 // transform retval and close
     116         351 :                 aRetval.transform(aArrowTransform);
     117         351 :                 aRetval.setClosed(true);
     118             : 
     119             :                 // if pConsumedLength is asked for, fill it
     120         351 :                 if(pConsumedLength)
     121             :                 {
     122         351 :                     *pConsumedLength = fConsumedLength;
     123         351 :                 }
     124             :             }
     125             : 
     126         351 :             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        1717 :         bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
     137             :         {
     138             :             // isBezier() is true, already tested by caller
     139        1717 :             const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
     140             : 
     141        1717 :             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        3434 :             const B2DVector aTangentA(rCandidate.getTangent(0.0));
     150        1717 :             const double fScalarAE(aEdge.scalar(aTangentA));
     151             : 
     152        1717 :             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        1717 :             const double fScalarE(aEdge.scalar(aEdge));
     160        1717 :             const double fScalarA(aTangentA.scalar(aTangentA));
     161        1717 :             const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
     162             : 
     163        1717 :             if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
     164             :             {
     165             :                 // length of TangentA is more than fMaxPartOfEdge of length of edge
     166         250 :                 return false;
     167             :             }
     168             : 
     169        1467 :             if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
     170             :             {
     171             :                 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
     172         225 :                 return false;
     173             :             }
     174             : 
     175             :             // get tangentB and scalar with edge
     176        2484 :             const B2DVector aTangentB(rCandidate.getTangent(1.0));
     177        1242 :             const double fScalarBE(aEdge.scalar(aTangentB));
     178             : 
     179        1242 :             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        1242 :             const double fScalarB(aTangentB.scalar(aTangentB));
     187             : 
     188        1242 :             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         930 :             if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
     195             :             {
     196             :                 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
     197           5 :                 return false;
     198             :             }
     199             : 
     200        2642 :             return true;
     201             :         }
     202             : 
     203        1915 :         void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
     204             :         {
     205        1915 :             if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
     206             :             {
     207        1123 :                 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
     208             :             }
     209             :             else
     210             :             {
     211        1584 :                 B2DCubicBezier aLeft, aRight;
     212         792 :                 rCandidate.split(0.5, &aLeft, &aRight);
     213             : 
     214         792 :                 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
     215        1584 :                 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
     216             :             }
     217        1915 :         }
     218             : 
     219       14795 :         B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
     220             :         {
     221       14795 :             const sal_uInt32 nPointCount(rCandidate.count());
     222             : 
     223       14795 :             if(rCandidate.areControlPointsUsed() && nPointCount)
     224             :             {
     225         181 :                 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
     226         181 :                 B2DPolygon aRetval;
     227         362 :                 B2DCubicBezier aEdge;
     228             : 
     229             :                 // prepare edge for loop
     230         181 :                 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
     231         181 :                 aRetval.append(aEdge.getStartPoint());
     232             : 
     233         497 :                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
     234             :                 {
     235             :                     // fill B2DCubicBezier
     236         316 :                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
     237         316 :                     aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
     238         316 :                     aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
     239         316 :                     aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
     240             : 
     241             :                     // get rid of unnecessary bezier segments
     242         316 :                     aEdge.testAndSolveTrivialBezier();
     243             : 
     244         316 :                     if(aEdge.isBezier())
     245             :                     {
     246             :                         // before splitting recursively with internal simple criteria, use
     247             :                         // ExtremumPosFinder to remove those
     248         293 :                         ::std::vector< double > aExtremumPositions;
     249             : 
     250         293 :                         aExtremumPositions.reserve(4);
     251         293 :                         aEdge.getAllExtremumPositions(aExtremumPositions);
     252             : 
     253         293 :                         const sal_uInt32 nCount(aExtremumPositions.size());
     254             : 
     255         293 :                         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         258 :                             impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
     310         293 :                         }
     311             :                     }
     312             :                     else
     313             :                     {
     314             :                         // straight edge, add point
     315          23 :                         aRetval.append(aEdge.getEndPoint());
     316             :                     }
     317             : 
     318             :                     // prepare edge for next step
     319         316 :                     aEdge.setStartPoint(aEdge.getEndPoint());
     320             :                 }
     321             : 
     322             :                 // copy closed flag and check for double points
     323         181 :                 aRetval.setClosed(rCandidate.isClosed());
     324         181 :                 aRetval.removeDoublePoints();
     325             : 
     326         362 :                 return aRetval;
     327             :             }
     328             :             else
     329             :             {
     330       14614 :                 return rCandidate;
     331             :             }
     332             :         }
     333             : 
     334       29067 :         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       29067 :             if(rEdge.isBezier())
     347             :             {
     348             :                 // prepare target and data common for upper and lower
     349        1123 :                 B2DPolygon aBezierPolygon;
     350        2246 :                 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
     351        1123 :                 const double fEdgeLength(aPureEdgeVector.getLength());
     352        1123 :                 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
     353        2246 :                 B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
     354        2246 :                 B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
     355        2246 :                 const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
     356        2246 :                 const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
     357             : 
     358             :                 // create upper displacement vectors and check if they cut
     359        2246 :                 const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
     360        2246 :                 const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
     361        1123 :                 double fCutA(0.0);
     362             :                 const CutFlagValue aCutA(tools::findCut(
     363             :                     rEdge.getStartPoint(), aPerpendStartA,
     364             :                     rEdge.getEndPoint(), aPerpendEndA,
     365        1123 :                     CutFlagValue::ALL, &fCutA));
     366        1123 :                 const bool bCutA(CutFlagValue::NONE != aCutA);
     367             : 
     368             :                 // create lower displacement vectors and check if they cut
     369        2246 :                 const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
     370        2246 :                 const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
     371        1123 :                 double fCutB(0.0);
     372             :                 const CutFlagValue aCutB(tools::findCut(
     373             :                     rEdge.getEndPoint(), aPerpendEndB,
     374             :                     rEdge.getStartPoint(), aPerpendStartB,
     375        1123 :                     CutFlagValue::ALL, &fCutB));
     376        1123 :                 const bool bCutB(CutFlagValue::NONE != aCutB);
     377             : 
     378             :                 // check if cut happens
     379        1123 :                 const bool bCut(bCutA || bCutB);
     380        2246 :                 B2DPoint aCutPoint;
     381             : 
     382             :                 // create left edge
     383        1123 :                 if(bStartRound || bStartSquare)
     384             :                 {
     385          10 :                     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           2 :                         const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
     400             : 
     401           2 :                         if(bCutB)
     402             :                         {
     403           0 :                             aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
     404             :                         }
     405             : 
     406           2 :                         aBezierPolygon.append(aStart + aPerpendStartB);
     407           2 :                         aBezierPolygon.append(aStart + aPerpendStartA);
     408             : 
     409           2 :                         if(bCutA)
     410             :                         {
     411           0 :                             aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
     412           2 :                         }
     413          10 :                     }
     414             :                 }
     415             :                 else
     416             :                 {
     417             :                     // append original in-between point
     418        1113 :                     aBezierPolygon.append(rEdge.getStartPoint());
     419             :                 }
     420             : 
     421             :                 // create upper edge.
     422             :                 {
     423        1123 :                     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        1030 :                         const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
     433        2060 :                         const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
     434        2060 :                         const B2DVector aEdge(aEnd - aStart);
     435        1030 :                         const double fLength(aEdge.getLength());
     436        1030 :                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
     437        2060 :                         const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
     438        2060 :                         const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
     439             : 
     440        1030 :                         aBezierPolygon.append(aStart);
     441        2060 :                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
     442             :                     }
     443             :                 }
     444             : 
     445             :                 // create right edge
     446        1123 :                 if(bEndRound || bEndSquare)
     447             :                 {
     448          10 :                     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           2 :                         const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
     463             : 
     464           2 :                         if(bCutA)
     465             :                         {
     466           0 :                             aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
     467             :                         }
     468             : 
     469           2 :                         aBezierPolygon.append(aEnd + aPerpendEndA);
     470           2 :                         aBezierPolygon.append(aEnd + aPerpendEndB);
     471             : 
     472           2 :                         if(bCutB)
     473             :                         {
     474           0 :                             aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
     475           2 :                         }
     476          10 :                     }
     477             :                 }
     478             :                 else
     479             :                 {
     480             :                     // append original in-between point
     481        1113 :                     aBezierPolygon.append(rEdge.getEndPoint());
     482             :                 }
     483             : 
     484             :                 // create lower edge.
     485             :                 {
     486        1123 :                     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        1007 :                         const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
     496        2014 :                         const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
     497        2014 :                         const B2DVector aEdge(aEnd - aStart);
     498        1007 :                         const double fLength(aEdge.getLength());
     499        1007 :                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
     500        2014 :                         const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
     501        2014 :                         const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
     502             : 
     503        1007 :                         aBezierPolygon.append(aStart);
     504        2014 :                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
     505             :                     }
     506             :                 }
     507             : 
     508             :                 // close
     509        1123 :                 aBezierPolygon.setClosed(true);
     510             : 
     511        1123 :                 if(bStartRound || bEndRound)
     512             :                 {
     513             :                     // double points possible when round caps are used at start or end
     514          16 :                     aBezierPolygon.removeDoublePoints();
     515             :                 }
     516             : 
     517        1123 :                 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        2246 :                 return aBezierPolygon;
     571             :             }
     572             :             else
     573             :             {
     574             :                 // Get start and  end point, create tangent and set to needed length
     575       27944 :                 B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
     576       27944 :                 aTangent.setLength(fHalfLineWidth);
     577             : 
     578             :                 // prepare return value
     579       55888 :                 B2DPolygon aEdgePolygon;
     580             : 
     581             :                 // buffered angle
     582       27944 :                 double fAngle(0.0);
     583       27944 :                 bool bAngle(false);
     584             : 
     585             :                 // buffered perpendicular
     586       55888 :                 B2DVector aPerpend;
     587       27944 :                 bool bPerpend(false);
     588             : 
     589             :                 // create left vertical
     590       27944 :                 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       27943 :                     aPerpend.setX(-aTangent.getY());
     605       27943 :                     aPerpend.setY(aTangent.getX());
     606       27943 :                     bPerpend = true;
     607             : 
     608       27943 :                     if(bStartSquare)
     609             :                     {
     610        2221 :                         const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
     611             : 
     612        2221 :                         aEdgePolygon.append(aStart + aPerpend);
     613        2221 :                         aEdgePolygon.append(aStart - aPerpend);
     614             :                     }
     615             :                     else
     616             :                     {
     617       25722 :                         aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
     618       25722 :                         aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
     619       25722 :                         aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
     620             :                     }
     621             :                 }
     622             : 
     623             :                 // create right vertical
     624       27944 :                 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       27943 :                     if(!bPerpend)
     644             :                     {
     645           0 :                         aPerpend.setX(-aTangent.getY());
     646           0 :                         aPerpend.setY(aTangent.getX());
     647             :                     }
     648             : 
     649       27943 :                     if(bEndSquare)
     650             :                     {
     651        2221 :                         const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
     652             : 
     653        2221 :                         aEdgePolygon.append(aEnd - aPerpend);
     654        2221 :                         aEdgePolygon.append(aEnd + aPerpend);
     655             :                     }
     656             :                     else
     657             :                     {
     658       25722 :                         aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
     659       25722 :                         aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
     660       25722 :                         aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
     661             :                     }
     662             :                 }
     663             : 
     664             :                 // close and return
     665       27944 :                 aEdgePolygon.setClosed(true);
     666             : 
     667       55888 :                 return aEdgePolygon;
     668             :             }
     669             :         }
     670             : 
     671       12329 :         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       12329 :             B2DPolygon aEdgePolygon;
     686       24658 :             const B2DPoint aStartPoint(rPoint + rPerpendPrev);
     687       24658 :             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       12329 :             if(B2DLineJoin::Miter == eJoin)
     692             :             {
     693        6634 :                 const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
     694             : 
     695        6634 :                 if((F_PI - fAngle) < fMiterMinimumAngle)
     696             :                 {
     697             :                     // fallback to bevel
     698          10 :                     eJoin = B2DLineJoin::Bevel;
     699             :                 }
     700             :             }
     701             : 
     702       12329 :             switch(eJoin)
     703             :             {
     704             :                 case B2DLineJoin::Miter :
     705             :                 {
     706        6624 :                     aEdgePolygon.append(aEndPoint);
     707        6624 :                     aEdgePolygon.append(rPoint);
     708        6624 :                     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        6624 :                     double fCutPos(0.0);
     716        6624 :                     tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CutFlagValue::ALL, &fCutPos);
     717             : 
     718        6624 :                     if(0.0 != fCutPos)
     719             :                     {
     720        6624 :                         const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
     721        6624 :                         aEdgePolygon.append(aCutPoint);
     722             :                     }
     723             : 
     724        6624 :                     break;
     725             :                 }
     726             :                 case B2DLineJoin::Round :
     727             :                 {
     728             :                     // use tooling to add needed EllipseSegment
     729        5695 :                     double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
     730        5695 :                     double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
     731             : 
     732             :                     // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
     733        5695 :                     if(fAngleStart < 0.0)
     734             :                     {
     735        2844 :                         fAngleStart += F_2PI;
     736             :                     }
     737             : 
     738        5695 :                     if(fAngleEnd < 0.0)
     739             :                     {
     740        2817 :                         fAngleEnd += F_2PI;
     741             :                     }
     742             : 
     743        5695 :                     const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
     744             : 
     745        5695 :                     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        5695 :                         aEdgePolygon = aBow;
     752        5695 :                         aEdgePolygon.setB2DPoint(0, aStartPoint);
     753        5695 :                         aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
     754        5695 :                         aEdgePolygon.append(rPoint);
     755             : 
     756        5695 :                         break;
     757             :                     }
     758             :                     else
     759             :                     {
     760             :                         // wanted fall-through to default
     761           0 :                     }
     762             :                 }
     763             :                 default: // B2DLineJoin::Bevel
     764             :                 {
     765          10 :                     aEdgePolygon.append(aEndPoint);
     766          10 :                     aEdgePolygon.append(rPoint);
     767          10 :                     aEdgePolygon.append(aStartPoint);
     768             : 
     769          10 :                     break;
     770             :                 }
     771             :             }
     772             : 
     773             :             // create last polygon part for edge
     774       12329 :             aEdgePolygon.setClosed(true);
     775             : 
     776       24658 :             return aEdgePolygon;
     777             :         }
     778             :     } // end of anonymus namespace
     779             : 
     780             :     namespace tools
     781             :     {
     782       14795 :         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       14795 :             if(fMaxAllowedAngle > F_PI2)
     792             :             {
     793           0 :                 fMaxAllowedAngle = F_PI2;
     794             :             }
     795       14795 :             else if(fMaxAllowedAngle < 0.01 * F_PI2)
     796             :             {
     797           0 :                 fMaxAllowedAngle = 0.01 * F_PI2;
     798             :             }
     799             : 
     800       14795 :             if(fMaxPartOfEdge > 1.0)
     801             :             {
     802           0 :                 fMaxPartOfEdge = 1.0;
     803             :             }
     804       14795 :             else if(fMaxPartOfEdge < 0.01)
     805             :             {
     806           0 :                 fMaxPartOfEdge = 0.01;
     807             :             }
     808             : 
     809       14795 :             if(fMiterMinimumAngle > F_PI)
     810             :             {
     811           0 :                 fMiterMinimumAngle = F_PI;
     812             :             }
     813       14795 :             else if(fMiterMinimumAngle < 0.01 * F_PI)
     814             :             {
     815           0 :                 fMiterMinimumAngle = 0.01 * F_PI;
     816             :             }
     817             : 
     818       14795 :             B2DPolygon aCandidate(rCandidate);
     819       14795 :             const double fMaxCos(cos(fMaxAllowedAngle));
     820             : 
     821       14795 :             aCandidate.removeDoublePoints();
     822       14795 :             aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
     823             : 
     824       14795 :             const sal_uInt32 nPointCount(aCandidate.count());
     825             : 
     826       14795 :             if(nPointCount)
     827             :             {
     828       14795 :                 B2DPolyPolygon aRetval;
     829       14795 :                 const bool bIsClosed(aCandidate.isClosed());
     830       14795 :                 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
     831       14795 :                 const bool bLineCap(!bIsClosed && com::sun::star::drawing::LineCap_BUTT != eCap);
     832             : 
     833       14795 :                 if(nEdgeCount)
     834             :                 {
     835       14730 :                     B2DCubicBezier aEdge;
     836       29460 :                     B2DCubicBezier aPrev;
     837             : 
     838       14730 :                     const bool bEventuallyCreateLineJoin(B2DLineJoin::NONE != eJoin);
     839             :                     // prepare edge
     840       14730 :                     aEdge.setStartPoint(aCandidate.getB2DPoint(0));
     841             : 
     842       14730 :                     if(bIsClosed && bEventuallyCreateLineJoin)
     843             :                     {
     844             :                         // prepare previous edge
     845         814 :                         const sal_uInt32 nPrevIndex(nPointCount - 1);
     846         814 :                         aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
     847         814 :                         aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
     848         814 :                         aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
     849         814 :                         aPrev.setEndPoint(aEdge.getStartPoint());
     850             :                     }
     851             : 
     852       43797 :                     for(sal_uInt32 a(0); a < nEdgeCount; a++)
     853             :                     {
     854             :                         // fill current Edge
     855       29067 :                         const sal_uInt32 nNextIndex((a + 1) % nPointCount);
     856       29067 :                         aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
     857       29067 :                         aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
     858       29067 :                         aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
     859             : 
     860             :                         // check and create linejoin
     861       29067 :                         if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
     862             :                         {
     863       15151 :                             B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
     864       30302 :                             B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
     865       15151 :                             B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
     866             : 
     867       15151 :                             if(B2VectorOrientation::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        2826 :                                    if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
     872             :                                    {
     873        2389 :                                     const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
     874             : 
     875        2389 :                                     if(fTools::equal(fAngle, F_PI))
     876             :                                     {
     877             :                                         // for half-circle production, fallback to positive
     878             :                                         // orientation
     879           4 :                                         aOrientation = B2VectorOrientation::Positive;
     880             :                                     }
     881             :                                 }
     882             :                             }
     883             : 
     884       15151 :                             if(B2VectorOrientation::Positive == aOrientation)
     885             :                             {
     886        9884 :                                 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
     887       19768 :                                 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       19768 :                                         fMiterMinimumAngle));
     899             :                             }
     900        5267 :                             else if(B2VectorOrientation::Negative == aOrientation)
     901             :                             {
     902        2445 :                                 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
     903        4890 :                                 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        4890 :                                         fMiterMinimumAngle));
     915       15151 :                             }
     916             :                         }
     917             : 
     918             :                         // create geometry for edge
     919       29067 :                         const bool bLast(a + 1 == nEdgeCount);
     920             : 
     921       29067 :                         if(bLineCap)
     922             :                         {
     923        2528 :                             const bool bFirst(!a);
     924             : 
     925             :                             aRetval.append(
     926             :                                 createAreaGeometryForEdge(
     927             :                                     aEdge,
     928             :                                     fHalfLineWidth,
     929        2528 :                                     bFirst && com::sun::star::drawing::LineCap_ROUND == eCap,
     930        2528 :                                     bLast && com::sun::star::drawing::LineCap_ROUND == eCap,
     931        2528 :                                     bFirst && com::sun::star::drawing::LineCap_SQUARE == eCap,
     932       10112 :                                     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       26539 :                                     false));
     944             :                         }
     945             : 
     946             :                         // prepare next step
     947       29067 :                         if(!bLast)
     948             :                         {
     949       14337 :                             if(bEventuallyCreateLineJoin)
     950             :                             {
     951       14337 :                                 aPrev = aEdge;
     952             :                             }
     953             : 
     954       14337 :                             aEdge.setStartPoint(aEdge.getEndPoint());
     955             :                         }
     956       14730 :                     }
     957             :                 }
     958             : 
     959       14795 :                 return aRetval;
     960             :             }
     961             :             else
     962             :             {
     963           0 :                 return B2DPolyPolygon(rCandidate);
     964       14795 :             }
     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.11