1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <basegfx/polygon/b2dpolygontools.hxx>
21 #include <basegfx/polygon/b2dpolypolygontools.hxx>
22 #include <basegfx/polygon/b2dpolypolygon.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
25 #include <rtl/ustring.hxx>
26 #include <rtl/math.hxx>
27 #include <stringconversiontools.hxx>
33 bool PointIndex::operator<(const PointIndex
& rComp
) const
35 if(rComp
.getPolygonIndex() == getPolygonIndex())
37 return rComp
.getPointIndex() < getPointIndex();
40 return rComp
.getPolygonIndex() < getPolygonIndex();
44 B2DPolyPolygon
& o_rPolyPolygon
,
45 const OUString
& rSvgDStatement
,
46 bool bHandleRelativeNextPointCompatible
,
47 PointIndexSet
* pHelpPointIndexSet
)
49 o_rPolyPolygon
.clear();
50 const sal_Int32
nLen(rSvgDStatement
.getLength());
56 // skip initial whitespace
57 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
61 bool bRelative(false);
62 const sal_Unicode
aCurrChar(rSvgDStatement
[nPos
]);
64 if(o_rPolyPolygon
.count() && !aCurrPoly
.count() && !('m' == aCurrChar
|| 'M' == aCurrChar
))
66 // we have a new sub-polygon starting, but without a 'moveto' command.
67 // this requires to add the current point as start point to the polygon
68 // (see SVG1.1 8.3.3 The "closepath" command)
69 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
77 // consume CurrChar and whitespace
79 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
81 // create closed polygon and reset import values
84 if(!bHandleRelativeNextPointCompatible
)
86 // SVG defines that "the next subpath starts at the
87 // same initial point as the current subpath", so set the
88 // current point if we do not need to be compatible
89 nLastX
= aCurrPoly
.getB2DPoint(0).getX();
90 nLastY
= aCurrPoly
.getB2DPoint(0).getY();
93 aCurrPoly
.setClosed(true);
94 o_rPolyPolygon
.append(aCurrPoly
);
104 // create non-closed polygon and reset import values
105 if(aCurrPoly
.count())
107 o_rPolyPolygon
.append(aCurrPoly
);
111 // FALLTHROUGH intended to add coordinate data as 1st point of new polygon
116 if('m' == aCurrChar
|| 'l' == aCurrChar
)
121 // consume CurrChar and whitespace
123 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
125 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
129 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
130 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
143 aCurrPoly
.append(B2DPoint(nX
, nY
));
151 // FALLTHROUGH intended
156 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
158 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
160 double nX
, nY(nLastY
);
162 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
173 aCurrPoly
.append(B2DPoint(nX
, nY
));
181 // FALLTHROUGH intended
186 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
188 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
190 double nX(nLastX
), nY
;
192 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
203 aCurrPoly
.append(B2DPoint(nX
, nY
));
211 // FALLTHROUGH intended
216 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
218 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
223 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
224 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
225 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
226 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
236 // ensure existance of start point
237 if(!aCurrPoly
.count())
239 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
242 // get first control point. It's the reflection of the PrevControlPoint
243 // of the last point. If not existent, use current point (see SVG)
244 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
245 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
247 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
249 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
250 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
252 // use mirrored previous control point
253 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
254 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
257 // append curved edge
258 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
270 // FALLTHROUGH intended
275 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
277 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
283 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
284 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
285 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
286 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
287 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
288 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
300 // ensure existance of start point
301 if(!aCurrPoly
.count())
303 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
306 // append curved edge
307 aCurrPoly
.appendBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
316 // #100617# quadratic beziers are imported as cubic ones
320 // FALLTHROUGH intended
325 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
327 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
332 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
333 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
334 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
335 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
345 // calculate the cubic bezier coefficients from the quadratic ones
346 const double nX1Prime((nX1
* 2.0 + nLastX
) / 3.0);
347 const double nY1Prime((nY1
* 2.0 + nLastY
) / 3.0);
348 const double nX2Prime((nX1
* 2.0 + nX
) / 3.0);
349 const double nY2Prime((nY1
* 2.0 + nY
) / 3.0);
351 // ensure existance of start point
352 if(!aCurrPoly
.count())
354 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
357 // append curved edge
358 aCurrPoly
.appendBezierSegment(B2DPoint(nX1Prime
, nY1Prime
), B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
367 // #100617# relative quadratic beziers are imported as cubic
371 // FALLTHROUGH intended
376 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
378 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
382 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
383 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
391 // ensure existance of start point
392 if(!aCurrPoly
.count())
394 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
397 // get first control point. It's the reflection of the PrevControlPoint
398 // of the last point. If not existent, use current point (see SVG)
399 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
400 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
401 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
403 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
405 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
407 // use mirrored previous control point
408 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
409 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
412 if(!aPrevControl
.equal(aPrevPoint
))
414 // there is a prev control point, and we have the already mirrored one
415 // in aPrevControl. We also need the quadratic control point for this
416 // new quadratic segment to calculate the 2nd cubic control point
417 const B2DPoint
aQuadControlPoint(
418 ((3.0 * aPrevControl
.getX()) - aPrevPoint
.getX()) / 2.0,
419 ((3.0 * aPrevControl
.getY()) - aPrevPoint
.getY()) / 2.0);
421 // calculate the cubic bezier coefficients from the quadratic ones.
422 const double nX2Prime((aQuadControlPoint
.getX() * 2.0 + nX
) / 3.0);
423 const double nY2Prime((aQuadControlPoint
.getY() * 2.0 + nY
) / 3.0);
425 // append curved edge, use mirrored cubic control point directly
426 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
430 // when no previous control, SVG says to use current point -> straight line.
431 // Just add end point
432 aCurrPoly
.append(B2DPoint(nX
, nY
));
445 // FALLTHROUGH intended
450 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
452 while(nPos
< nLen
&& ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement
, nPos
))
455 double fRX
, fRY
, fPhi
;
456 sal_Int32 bLargeArcFlag
, bSweepFlag
;
458 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRX
, nPos
, rSvgDStatement
, nLen
)) return false;
459 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRY
, nPos
, rSvgDStatement
, nLen
)) return false;
460 if(!::basegfx::internal::lcl_importDoubleAndSpaces(fPhi
, nPos
, rSvgDStatement
, nLen
)) return false;
461 if(!::basegfx::internal::lcl_importFlagAndSpaces(bLargeArcFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
462 if(!::basegfx::internal::lcl_importFlagAndSpaces(bSweepFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
463 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
464 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
472 if( nX
== nLastX
&& nY
== nLastY
)
473 continue; // start==end -> skip according to SVG spec
475 if( fRX
== 0.0 || fRY
== 0.0 )
477 // straight line segment according to SVG spec
478 aCurrPoly
.append(B2DPoint(nX
, nY
));
482 // normalize according to SVG spec
483 fRX
=fabs(fRX
); fRY
=fabs(fRY
);
485 // from the SVG spec, appendix F.6.4
487 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
488 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
489 const B2DPoint
p1(nLastX
, nLastY
);
490 const B2DPoint
p2(nX
, nY
);
491 B2DHomMatrix
aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi
*M_PI
/180));
493 const B2DPoint
p1_prime( aTransform
* B2DPoint(((p1
-p2
)/2.0)) );
495 // ______________________________________ rx y1'
496 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
497 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
499 // chose + if f_A != f_S
500 // chose - if f_A = f_S
501 B2DPoint aCenter_prime
;
502 const double fRadicant(
503 (fRX
*fRX
*fRY
*fRY
- fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() - fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX())/
504 (fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() + fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX()));
505 if( fRadicant
< 0.0 )
507 // no solution - according to SVG
508 // spec, scale up ellipse
509 // uniformly such that it passes
510 // through end points (denominator
511 // of radicant solved for fRY,
513 const double fRatio(fRX
/fRY
);
514 const double fRadicant2(
515 p1_prime
.getY()*p1_prime
.getY() +
516 p1_prime
.getX()*p1_prime
.getX()/(fRatio
*fRatio
));
517 if( fRadicant2
< 0.0 )
519 // only trivial solution, one
520 // of the axes 0 -> straight
521 // line segment according to
523 aCurrPoly
.append(B2DPoint(nX
, nY
));
527 fRY
=sqrt(fRadicant2
);
530 // keep center_prime forced to (0,0)
534 const double fFactor(
535 (bLargeArcFlag
==bSweepFlag
? -1.0 : 1.0) *
538 // actually calculate center_prime
539 aCenter_prime
= B2DPoint(
540 fFactor
*fRX
*p1_prime
.getY()/fRY
,
541 -fFactor
*fRY
*p1_prime
.getX()/fRX
);
545 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
548 // 1 | (x1' - cx')/rx |
549 // theta1 = angle(( ), | | )
550 // 0 | (y1' - cy')/ry |
551 const B2DPoint
aRadii(fRX
,fRY
);
553 B2DVector(1.0,0.0).angle(
554 (p1_prime
-aCenter_prime
)/aRadii
));
556 // |1| | (-x1' - cx')/rx |
557 // theta2 = angle( | | , | | )
558 // |0| | (-y1' - cy')/ry |
560 B2DVector(1.0,0.0).angle(
561 (-p1_prime
-aCenter_prime
)/aRadii
));
563 // map both angles to [0,2pi)
564 fTheta1
= fmod(2*M_PI
+fTheta1
,2*M_PI
);
565 fTheta2
= fmod(2*M_PI
+fTheta2
,2*M_PI
);
567 // make sure the large arc is taken
569 // createPolygonFromEllipseSegment()
570 // normalizes to e.g. cw arc)
572 std::swap(fTheta1
,fTheta2
);
574 // finally, create bezier polygon from this
576 tools::createPolygonFromUnitEllipseSegment(
579 // transform ellipse by rotation & move to final center
580 aTransform
= basegfx::tools::createScaleB2DHomMatrix(fRX
, fRY
);
581 aTransform
.translate(aCenter_prime
.getX(),
582 aCenter_prime
.getY());
583 aTransform
.rotate(fPhi
*M_PI
/180);
584 const B2DPoint
aOffset((p1
+p2
)/2.0);
585 aTransform
.translate(aOffset
.getX(),
587 aSegment
.transform(aTransform
);
589 // createPolygonFromEllipseSegment()
590 // always creates arcs that are
591 // positively oriented - flip polygon
592 // if we swapped angles above
596 // remember PointIndex of evtl. added pure helper points
597 sal_uInt32
nPointIndex(aCurrPoly
.count() + 1);
598 aCurrPoly
.append(aSegment
);
600 // if asked for, mark pure helper points by adding them to the index list of
602 if(pHelpPointIndexSet
&& aCurrPoly
.count() > 1)
604 const sal_uInt32
nPolyIndex(o_rPolyPolygon
.count());
606 for(;nPointIndex
+ 1 < aCurrPoly
.count(); nPointIndex
++)
608 pHelpPointIndexSet
->insert(PointIndex(nPolyIndex
, nPointIndex
));
622 OSL_FAIL("importFromSvgD(): skipping tags in svg:d element (unknown)!");
623 OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar
);
630 // if there is polygon data, create non-closed polygon
631 if(aCurrPoly
.count())
633 o_rPolyPolygon
.append(aCurrPoly
);
639 bool importFromSvgPoints( B2DPolygon
& o_rPoly
,
640 const OUString
& rSvgPointsAttribute
)
643 const sal_Int32
nLen(rSvgPointsAttribute
.getLength());
647 // skip initial whitespace
648 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
652 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
653 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
656 o_rPoly
.append(B2DPoint(nX
, nY
));
658 // skip to next number, or finish
659 ::basegfx::internal::lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
665 OUString
exportToSvgPoints( const B2DPolygon
& rPoly
)
667 OSL_ENSURE(!rPoly
.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)");
668 const sal_uInt32
nPointCount(rPoly
.count());
669 OUStringBuffer aResult
;
671 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
673 const basegfx::B2DPoint
aPoint(rPoly
.getB2DPoint(a
));
680 ::basegfx::internal::lcl_putNumberChar(aResult
, aPoint
.getX());
682 ::basegfx::internal::lcl_putNumberChar(aResult
, aPoint
.getY());
685 return aResult
.makeStringAndClear();
688 OUString
exportToSvgD(
689 const B2DPolyPolygon
& rPolyPolygon
,
690 bool bUseRelativeCoordinates
,
691 bool bDetectQuadraticBeziers
,
692 bool bHandleRelativeNextPointCompatible
)
694 const sal_uInt32
nCount(rPolyPolygon
.count());
695 OUStringBuffer aResult
;
696 B2DPoint
aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
698 for(sal_uInt32
i(0); i
< nCount
; i
++)
700 const B2DPolygon
aPolygon(rPolyPolygon
.getB2DPolygon(i
));
701 const sal_uInt32
nPointCount(aPolygon
.count());
705 const bool bPolyUsesControlPoints(aPolygon
.areControlPointsUsed());
706 const sal_uInt32
nEdgeCount(aPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
707 sal_Unicode
aLastSVGCommand(' '); // last SVG command char
708 B2DPoint aLeft
, aRight
; // for quadratic bezier test
710 // handle polygon start point
711 B2DPoint
aEdgeStart(aPolygon
.getB2DPoint(0));
712 bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates
);
714 if(bHandleRelativeNextPointCompatible
)
716 // To get around the error that the start point for the next polygon is the
717 // start point of the current one (and not the last as it was handled up to now)
718 // do force to write an absolute 'M' command as start for the next polygon
719 bUseRelativeCoordinatesForFirstPoint
= false;
722 // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
723 aResult
.append(::basegfx::internal::lcl_getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint
));
724 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinatesForFirstPoint
);
725 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinatesForFirstPoint
);
726 aLastSVGCommand
= ::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint
);
727 aCurrentSVGPosition
= aEdgeStart
;
729 for(sal_uInt32
nIndex(0); nIndex
< nEdgeCount
; nIndex
++)
731 // prepare access to next point
732 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
733 const B2DPoint
aEdgeEnd(aPolygon
.getB2DPoint(nNextIndex
));
735 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
736 const bool bEdgeIsBezier(bPolyUsesControlPoints
737 && (aPolygon
.isNextControlPointUsed(nIndex
) || aPolygon
.isPrevControlPointUsed(nNextIndex
)));
741 // handle bezier edge
742 const B2DPoint
aControlEdgeStart(aPolygon
.getNextControlPoint(nIndex
));
743 const B2DPoint
aControlEdgeEnd(aPolygon
.getPrevControlPoint(nNextIndex
));
744 bool bIsQuadraticBezier(false);
746 // check continuity at current edge's start point. For SVG, do NOT use an
747 // existing continuity since no 'S' or 's' statement should be written. At
748 // import, that 'previous' control vector is not available. SVG documentation
749 // says for interpretation:
751 // "(If there is no previous command or if the previous command was
752 // not an C, c, S or s, assume the first control point is coincident
753 // with the current point.)"
755 // That's what is done from our import, so avoid exporting it as first statement
757 const bool bSymmetricAtEdgeStart(
759 && CONTINUITY_C2
== aPolygon
.getContinuityInPoint(nIndex
));
761 if(bDetectQuadraticBeziers
)
763 // check for quadratic beziers - that's
764 // the case if both control points are in
765 // the same place when they are prolonged
766 // to the common quadratic control point
768 // Left: P = (3P1 - P0) / 2
769 // Right: P = (3P2 - P3) / 2
770 aLeft
= B2DPoint((3.0 * aControlEdgeStart
- aEdgeStart
) / 2.0);
771 aRight
= B2DPoint((3.0 * aControlEdgeEnd
- aEdgeEnd
) / 2.0);
772 bIsQuadraticBezier
= aLeft
.equal(aRight
);
775 if(bIsQuadraticBezier
)
777 // approximately equal, export as quadratic bezier
778 if(bSymmetricAtEdgeStart
)
780 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('T', 't', bUseRelativeCoordinates
));
782 if(aLastSVGCommand
!= aCommand
)
784 aResult
.append(aCommand
);
785 aLastSVGCommand
= aCommand
;
788 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
789 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
790 aLastSVGCommand
= aCommand
;
791 aCurrentSVGPosition
= aEdgeEnd
;
795 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('Q', 'q', bUseRelativeCoordinates
));
797 if(aLastSVGCommand
!= aCommand
)
799 aResult
.append(aCommand
);
800 aLastSVGCommand
= aCommand
;
803 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aLeft
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
804 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aLeft
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
805 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
806 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
807 aLastSVGCommand
= aCommand
;
808 aCurrentSVGPosition
= aEdgeEnd
;
813 // export as cubic bezier
814 if(bSymmetricAtEdgeStart
)
816 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('S', 's', bUseRelativeCoordinates
));
818 if(aLastSVGCommand
!= aCommand
)
820 aResult
.append(aCommand
);
821 aLastSVGCommand
= aCommand
;
824 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
825 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
826 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
827 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
828 aLastSVGCommand
= aCommand
;
829 aCurrentSVGPosition
= aEdgeEnd
;
833 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('C', 'c', bUseRelativeCoordinates
));
835 if(aLastSVGCommand
!= aCommand
)
837 aResult
.append(aCommand
);
838 aLastSVGCommand
= aCommand
;
841 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
842 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
843 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
844 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
845 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
846 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
847 aLastSVGCommand
= aCommand
;
848 aCurrentSVGPosition
= aEdgeEnd
;
857 // it's a closed polygon's last edge and it's not a bezier edge, so there is
858 // no need to write it
862 const bool bXEqual(aEdgeStart
.getX() == aEdgeEnd
.getX());
863 const bool bYEqual(aEdgeStart
.getY() == aEdgeEnd
.getY());
865 if(bXEqual
&& bYEqual
)
867 // point is a double point; do not export at all
871 // export as vertical line
872 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('V', 'v', bUseRelativeCoordinates
));
874 if(aLastSVGCommand
!= aCommand
)
876 aResult
.append(aCommand
);
877 aLastSVGCommand
= aCommand
;
880 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
881 aCurrentSVGPosition
= aEdgeEnd
;
885 // export as horizontal line
886 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('H', 'h', bUseRelativeCoordinates
));
888 if(aLastSVGCommand
!= aCommand
)
890 aResult
.append(aCommand
);
891 aLastSVGCommand
= aCommand
;
894 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
895 aCurrentSVGPosition
= aEdgeEnd
;
900 const sal_Unicode
aCommand(::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinates
));
902 if(aLastSVGCommand
!= aCommand
)
904 aResult
.append(aCommand
);
905 aLastSVGCommand
= aCommand
;
908 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
909 ::basegfx::internal::lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
910 aCurrentSVGPosition
= aEdgeEnd
;
915 // prepare edge start for next loop step
916 aEdgeStart
= aEdgeEnd
;
919 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
920 if(aPolygon
.isClosed())
922 aResult
.append(::basegfx::internal::lcl_getCommand('Z', 'z', bUseRelativeCoordinates
));
925 if(!bHandleRelativeNextPointCompatible
)
927 // SVG defines that "the next subpath starts at the same initial point as the current subpath",
928 // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
929 aCurrentSVGPosition
= aPolygon
.getB2DPoint(0);
934 return aResult
.makeStringAndClear();
939 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */