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>
26 #include <rtl/ustring.hxx>
27 #include <sal/log.hxx>
28 #include <rtl/math.hxx>
29 #include <rtl/character.hxx>
30 #include <stringconversiontools.hxx>
35 void putCommandChar(OUStringBuffer
& rBuffer
,sal_Unicode
& rLastSVGCommand
, sal_Unicode aChar
, bool bToLower
,bool bVerbose
)
37 const sal_Unicode aCommand
= bToLower
? rtl::toAsciiLowerCase(aChar
) : aChar
;
39 if (bVerbose
&& rBuffer
.getLength())
42 if (bVerbose
|| rLastSVGCommand
!= aCommand
)
44 rBuffer
.append(aCommand
);
45 rLastSVGCommand
= aCommand
;
49 void putNumberChar(OUStringBuffer
& rStr
,double fValue
, double fOldValue
, bool bUseRelativeCoordinates
,bool bVerbose
)
51 if (bUseRelativeCoordinates
)
54 const sal_Int32
aLen(rStr
.getLength());
55 if (bVerbose
|| (aLen
&& basegfx::internal::isOnNumberChar(rStr
[aLen
- 1], false) && fValue
>= 0.0))
63 namespace basegfx::utils
65 bool PointIndex::operator<(const PointIndex
& rComp
) const
67 if(rComp
.getPolygonIndex() == getPolygonIndex())
69 return rComp
.getPointIndex() < getPointIndex();
72 return rComp
.getPolygonIndex() < getPolygonIndex();
76 B2DPolyPolygon
& o_rPolyPolygon
,
77 std::u16string_view rSvgDStatement
,
78 bool bHandleRelativeNextPointCompatible
,
79 PointIndexSet
* pHelpPointIndexSet
)
81 o_rPolyPolygon
.clear();
82 const sal_Int32
nLen(rSvgDStatement
.size());
88 // skip initial whitespace
89 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
93 bool bRelative(false);
94 const sal_Unicode
aCurrChar(rSvgDStatement
[nPos
]);
96 if(o_rPolyPolygon
.count() && !aCurrPoly
.count() && aCurrChar
!= 'm' && aCurrChar
!= 'M')
98 // we have a new sub-polygon starting, but without a 'moveto' command.
99 // this requires to add the current point as start point to the polygon
100 // (see SVG1.1 8.3.3 The "closepath" command)
101 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
109 // consume CurrChar and whitespace
111 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
113 // create closed polygon and reset import values
114 if(aCurrPoly
.count())
116 if(!bHandleRelativeNextPointCompatible
)
118 // SVG defines that "the next subpath starts at the
119 // same initial point as the current subpath", so set the
120 // current point if we do not need to be compatible
121 nLastX
= aCurrPoly
.getB2DPoint(0).getX();
122 nLastY
= aCurrPoly
.getB2DPoint(0).getY();
125 aCurrPoly
.setClosed(true);
126 o_rPolyPolygon
.append(aCurrPoly
);
136 // create non-closed polygon and reset import values
137 if(aCurrPoly
.count())
139 o_rPolyPolygon
.append(aCurrPoly
);
142 [[fallthrough
]]; // to add coordinate data as 1st point of new polygon
147 if(aCurrChar
== 'm' || aCurrChar
== 'l')
152 // consume CurrChar and whitespace
154 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
156 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
160 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
161 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
174 aCurrPoly
.append(B2DPoint(nX
, nY
));
187 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
189 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
191 double nX
, nY(nLastY
);
193 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
204 aCurrPoly
.append(B2DPoint(nX
, nY
));
217 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
219 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
221 double nX(nLastX
), nY
;
223 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
234 aCurrPoly
.append(B2DPoint(nX
, nY
));
247 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
249 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
254 if(!basegfx::internal::importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
255 if(!basegfx::internal::importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
256 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
257 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
267 // ensure existence of start point
268 if(!aCurrPoly
.count())
270 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
273 // get first control point. It's the reflection of the PrevControlPoint
274 // of the last point. If not existent, use current point (see SVG)
275 B2DPoint
aPrevControl(nLastX
, nLastY
);
276 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
278 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
280 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
281 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
283 // use mirrored previous control point
284 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
285 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
288 // append curved edge
289 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
306 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
308 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
314 if(!basegfx::internal::importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
315 if(!basegfx::internal::importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
316 if(!basegfx::internal::importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
317 if(!basegfx::internal::importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
318 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
319 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
331 // ensure existence of start point
332 if(!aCurrPoly
.count())
334 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
337 // append curved edge
338 aCurrPoly
.appendBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
347 // #100617# quadratic beziers are imported as cubic ones
356 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
358 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
363 if(!basegfx::internal::importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
364 if(!basegfx::internal::importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
365 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
366 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
376 // ensure existence of start point
377 if(!aCurrPoly
.count())
379 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
382 // append curved edge
383 aCurrPoly
.appendQuadraticBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX
, nY
));
392 // #100617# relative quadratic beziers are imported as cubic
401 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
403 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
407 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
408 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
416 // ensure existence of start point
417 if(!aCurrPoly
.count())
419 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
422 // get first control point. It's the reflection of the PrevControlPoint
423 // of the last point. If not existent, use current point (see SVG)
424 B2DPoint
aPrevControl(nLastX
, nLastY
);
425 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
426 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
428 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
430 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
432 // use mirrored previous control point
433 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
434 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
437 if(!aPrevControl
.equal(aPrevPoint
))
439 // there is a prev control point, and we have the already mirrored one
440 // in aPrevControl. We also need the quadratic control point for this
441 // new quadratic segment to calculate the 2nd cubic control point
442 const B2DPoint
aQuadControlPoint(
443 ((3.0 * aPrevControl
.getX()) - aPrevPoint
.getX()) / 2.0,
444 ((3.0 * aPrevControl
.getY()) - aPrevPoint
.getY()) / 2.0);
446 // calculate the cubic bezier coefficients from the quadratic ones.
447 const double nX2Prime((aQuadControlPoint
.getX() * 2.0 + nX
) / 3.0);
448 const double nY2Prime((aQuadControlPoint
.getY() * 2.0 + nY
) / 3.0);
450 // append curved edge, use mirrored cubic control point directly
451 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
455 // when no previous control, SVG says to use current point -> straight line.
456 // Just add end point
457 aCurrPoly
.append(B2DPoint(nX
, nY
));
475 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
477 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
480 double fRX
, fRY
, fPhi
;
481 sal_Int32 bLargeArcFlag
, bSweepFlag
;
483 if(!basegfx::internal::importDoubleAndSpaces(fRX
, nPos
, rSvgDStatement
, nLen
)) return false;
484 if(!basegfx::internal::importDoubleAndSpaces(fRY
, nPos
, rSvgDStatement
, nLen
)) return false;
485 if(!basegfx::internal::importDoubleAndSpaces(fPhi
, nPos
, rSvgDStatement
, nLen
)) return false;
486 if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
487 if(!basegfx::internal::importFlagAndSpaces(bSweepFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
488 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
489 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
497 if( rtl::math::approxEqual(nX
, nLastX
) && rtl::math::approxEqual(nY
, nLastY
) )
498 continue; // start==end -> skip according to SVG spec
500 if( fRX
== 0.0 || fRY
== 0.0 )
502 // straight line segment according to SVG spec
503 aCurrPoly
.append(B2DPoint(nX
, nY
));
507 // normalize according to SVG spec
508 fRX
=fabs(fRX
); fRY
=fabs(fRY
);
510 // from the SVG spec, appendix F.6.4
512 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
513 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
514 const B2DPoint
p1(nLastX
, nLastY
);
515 const B2DPoint
p2(nX
, nY
);
516 B2DHomMatrix
aTransform(basegfx::utils::createRotateB2DHomMatrix(
519 const B2DPoint
p1_prime( aTransform
* B2DPoint(((p1
-p2
)/2.0)) );
521 // ______________________________________ rx y1'
522 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
523 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
525 // chose + if f_A != f_S
526 // chose - if f_A = f_S
527 B2DPoint aCenter_prime
;
528 const double fRadicant(
529 (fRX
*fRX
*fRY
*fRY
- fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() - fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX())/
530 (fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() + fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX()));
531 if( fRadicant
< 0.0 )
533 // no solution - according to SVG
534 // spec, scale up ellipse
535 // uniformly such that it passes
536 // through end points (denominator
537 // of radicant solved for fRY,
539 const double fRatio(fRX
/fRY
);
540 const double fRadicant2(
541 p1_prime
.getY()*p1_prime
.getY() +
542 p1_prime
.getX()*p1_prime
.getX()/(fRatio
*fRatio
));
543 if( fRadicant2
< 0.0 )
545 // only trivial solution, one
546 // of the axes 0 -> straight
547 // line segment according to
549 aCurrPoly
.append(B2DPoint(nX
, nY
));
553 fRY
=sqrt(fRadicant2
);
556 // keep center_prime forced to (0,0)
560 const double fFactor(
561 (bLargeArcFlag
==bSweepFlag
? -1.0 : 1.0) *
564 // actually calculate center_prime
565 aCenter_prime
= B2DPoint(
566 fFactor
*fRX
*p1_prime
.getY()/fRY
,
567 -fFactor
*fRY
*p1_prime
.getX()/fRX
);
571 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
574 // 1 | (x1' - cx')/rx |
575 // theta1 = angle(( ), | | )
576 // 0 | (y1' - cy')/ry |
577 const B2DPoint
aRadii(fRX
,fRY
);
579 B2DVector(1.0,0.0).angle(
580 (p1_prime
-aCenter_prime
)/aRadii
));
582 // |1| | (-x1' - cx')/rx |
583 // theta2 = angle( | | , | | )
584 // |0| | (-y1' - cy')/ry |
586 B2DVector(1.0,0.0).angle(
587 (-p1_prime
-aCenter_prime
)/aRadii
));
589 // map both angles to [0,2pi)
590 fTheta1
= fmod(2*M_PI
+fTheta1
,2*M_PI
);
591 fTheta2
= fmod(2*M_PI
+fTheta2
,2*M_PI
);
593 // make sure the large arc is taken
595 // createPolygonFromEllipseSegment()
596 // normalizes to e.g. cw arc)
598 std::swap(fTheta1
,fTheta2
);
600 // finally, create bezier polygon from this
602 utils::createPolygonFromUnitEllipseSegment(
605 // transform ellipse by rotation & move to final center
606 aTransform
= basegfx::utils::createScaleB2DHomMatrix(fRX
, fRY
);
607 aTransform
.translate(aCenter_prime
.getX(),
608 aCenter_prime
.getY());
609 aTransform
.rotate(deg2rad(fPhi
));
610 const B2DPoint
aOffset((p1
+p2
)/2.0);
611 aTransform
.translate(aOffset
.getX(),
613 aSegment
.transform(aTransform
);
615 // createPolygonFromEllipseSegment()
616 // always creates arcs that are
617 // positively oriented - flip polygon
618 // if we swapped angles above
622 // remember PointIndex of evtl. added pure helper points
623 sal_uInt32
nPointIndex(aCurrPoly
.count() + 1);
624 aCurrPoly
.append(aSegment
);
626 // if asked for, mark pure helper points by adding them to the index list of
628 if(pHelpPointIndexSet
&& aCurrPoly
.count() > 1)
630 const sal_uInt32
nPolyIndex(o_rPolyPolygon
.count());
632 for(;nPointIndex
+ 1 < aCurrPoly
.count(); nPointIndex
++)
634 pHelpPointIndexSet
->insert(PointIndex(nPolyIndex
, nPointIndex
));
648 SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
649 << OUString(aCurrChar
)
657 // if there is polygon data, create non-closed polygon
658 if(aCurrPoly
.count())
660 o_rPolyPolygon
.append(aCurrPoly
);
666 bool importFromSvgPoints( B2DPolygon
& o_rPoly
,
667 std::u16string_view rSvgPointsAttribute
)
670 const sal_Int32
nLen(rSvgPointsAttribute
.size());
674 // skip initial whitespace
675 basegfx::internal::skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
679 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
680 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
683 o_rPoly
.append(B2DPoint(nX
, nY
));
685 // skip to next number, or finish
686 basegfx::internal::skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
692 OUString
exportToSvgPoints( const B2DPolygon
& rPoly
)
694 SAL_WARN_IF(rPoly
.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
695 const sal_uInt32
nPointCount(rPoly
.count());
696 OUStringBuffer aResult
;
698 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
700 const basegfx::B2DPoint
aPoint(rPoly
.getB2DPoint(a
));
707 aResult
.append(OUString::number(aPoint
.getX())
709 + OUString::number(aPoint
.getY()));
712 return aResult
.makeStringAndClear();
715 OUString
exportToSvgD(
716 const B2DPolyPolygon
& rPolyPolygon
,
717 bool bUseRelativeCoordinates
,
718 bool bDetectQuadraticBeziers
,
719 bool bHandleRelativeNextPointCompatible
,
720 bool bOOXMLMotionPath
)
722 const sal_uInt32
nCount(rPolyPolygon
.count());
723 sal_uInt32 nCombinedPointCount
= 0;
724 for(sal_uInt32
i(0); i
< nCount
; i
++)
726 const B2DPolygon
& aPolygon(rPolyPolygon
.getB2DPolygon(i
));
727 nCombinedPointCount
+= aPolygon
.count();
730 OUStringBuffer
aResult(std::max
<int>(nCombinedPointCount
* 32,512));
731 B2DPoint
aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
733 for(sal_uInt32
i(0); i
< nCount
; i
++)
735 const B2DPolygon
& aPolygon(rPolyPolygon
.getB2DPolygon(i
));
736 const sal_uInt32
nPointCount(aPolygon
.count());
740 const bool bPolyUsesControlPoints(aPolygon
.areControlPointsUsed());
741 const sal_uInt32
nEdgeCount(aPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
742 sal_Unicode
aLastSVGCommand(' '); // last SVG command char
743 B2DPoint aLeft
, aRight
; // for quadratic bezier test
745 // handle polygon start point
746 B2DPoint
aEdgeStart(aPolygon
.getB2DPoint(0));
747 bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates
);
749 if(bHandleRelativeNextPointCompatible
)
751 // To get around the error that the start point for the next polygon is the
752 // start point of the current one (and not the last as it was handled up to now)
753 // do force to write an absolute 'M' command as start for the next polygon
754 bUseRelativeCoordinatesForFirstPoint
= false;
757 // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
758 putCommandChar(aResult
, aLastSVGCommand
, 'M', bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
759 putNumberChar(aResult
, aEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
760 putNumberChar(aResult
, aEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
761 aLastSVGCommand
= bUseRelativeCoordinatesForFirstPoint
? 'l' : 'L';
762 aCurrentSVGPosition
= aEdgeStart
;
764 for(sal_uInt32
nIndex(0); nIndex
< nEdgeCount
; nIndex
++)
766 // prepare access to next point
767 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
768 const B2DPoint
aEdgeEnd(aPolygon
.getB2DPoint(nNextIndex
));
770 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
771 const bool bEdgeIsBezier(bPolyUsesControlPoints
772 && (aPolygon
.isNextControlPointUsed(nIndex
) || aPolygon
.isPrevControlPointUsed(nNextIndex
)));
776 // handle bezier edge
777 const B2DPoint
aControlEdgeStart(aPolygon
.getNextControlPoint(nIndex
));
778 const B2DPoint
aControlEdgeEnd(aPolygon
.getPrevControlPoint(nNextIndex
));
779 bool bIsQuadraticBezier(false);
781 // check continuity at current edge's start point. For SVG, do NOT use an
782 // existing continuity since no 'S' or 's' statement should be written. At
783 // import, that 'previous' control vector is not available. SVG documentation
784 // says for interpretation:
786 // "(If there is no previous command or if the previous command was
787 // not a C, c, S or s, assume the first control point is coincident
788 // with the current point.)"
790 // That's what is done from our import, so avoid exporting it as first statement
792 const bool bSymmetricAtEdgeStart(
793 !bOOXMLMotionPath
&& nIndex
!= 0
794 && aPolygon
.getContinuityInPoint(nIndex
) == B2VectorContinuity::C2
);
796 if(bDetectQuadraticBeziers
)
798 // check for quadratic beziers - that's
799 // the case if both control points are in
800 // the same place when they are prolonged
801 // to the common quadratic control point
803 // Left: P = (3P1 - P0) / 2
804 // Right: P = (3P2 - P3) / 2
805 aLeft
= B2DPoint((3.0 * aControlEdgeStart
- aEdgeStart
) / 2.0);
806 aRight
= B2DPoint((3.0 * aControlEdgeEnd
- aEdgeEnd
) / 2.0);
807 bIsQuadraticBezier
= aLeft
.equal(aRight
);
810 if(bIsQuadraticBezier
)
812 // approximately equal, export as quadratic bezier
813 if(bSymmetricAtEdgeStart
)
815 putCommandChar(aResult
, aLastSVGCommand
, 'T', bUseRelativeCoordinates
, bOOXMLMotionPath
);
817 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
818 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
819 aCurrentSVGPosition
= aEdgeEnd
;
823 putCommandChar(aResult
, aLastSVGCommand
, 'Q', bUseRelativeCoordinates
, bOOXMLMotionPath
);
825 putNumberChar(aResult
, aLeft
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
826 putNumberChar(aResult
, aLeft
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
827 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
828 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
829 aCurrentSVGPosition
= aEdgeEnd
;
834 // export as cubic bezier
835 if(bSymmetricAtEdgeStart
)
837 putCommandChar(aResult
, aLastSVGCommand
, 'S', bUseRelativeCoordinates
, bOOXMLMotionPath
);
839 putNumberChar(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
840 putNumberChar(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
841 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
842 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
843 aCurrentSVGPosition
= aEdgeEnd
;
847 putCommandChar(aResult
, aLastSVGCommand
, 'C', bUseRelativeCoordinates
, bOOXMLMotionPath
);
849 putNumberChar(aResult
, aControlEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
850 putNumberChar(aResult
, aControlEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
851 putNumberChar(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
852 putNumberChar(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
853 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
854 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
855 aCurrentSVGPosition
= aEdgeEnd
;
864 // it's a closed polygon's last edge and it's not a bezier edge, so there is
865 // no need to write it
869 const bool bXEqual(rtl::math::approxEqual(aEdgeStart
.getX(), aEdgeEnd
.getX()));
870 const bool bYEqual(rtl::math::approxEqual(aEdgeStart
.getY(), aEdgeEnd
.getY()));
872 if(bXEqual
&& bYEqual
)
874 // point is a double point; do not export at all
876 else if(bXEqual
&& !bOOXMLMotionPath
)
878 // export as vertical line
879 putCommandChar(aResult
, aLastSVGCommand
, 'V', bUseRelativeCoordinates
, bOOXMLMotionPath
);
881 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
882 aCurrentSVGPosition
= aEdgeEnd
;
884 else if(bYEqual
&& !bOOXMLMotionPath
)
886 // export as horizontal line
887 putCommandChar(aResult
, aLastSVGCommand
, 'H', bUseRelativeCoordinates
, bOOXMLMotionPath
);
889 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
890 aCurrentSVGPosition
= aEdgeEnd
;
895 putCommandChar(aResult
, aLastSVGCommand
, 'L', bUseRelativeCoordinates
, bOOXMLMotionPath
);
897 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
898 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
899 aCurrentSVGPosition
= aEdgeEnd
;
904 // prepare edge start for next loop step
905 aEdgeStart
= aEdgeEnd
;
908 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
909 if(aPolygon
.isClosed())
911 putCommandChar(aResult
, aLastSVGCommand
, 'Z', bUseRelativeCoordinates
, bOOXMLMotionPath
);
913 else if (bOOXMLMotionPath
)
915 putCommandChar(aResult
, aLastSVGCommand
, 'E', bUseRelativeCoordinates
, bOOXMLMotionPath
);
918 if(!bHandleRelativeNextPointCompatible
)
920 // SVG defines that "the next subpath starts at the same initial point as the current subpath",
921 // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
922 aCurrentSVGPosition
= aPolygon
.getB2DPoint(0);
927 return aResult
.makeStringAndClear();
931 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */