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: */
|