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))
67 bool PointIndex::operator<(const PointIndex
& rComp
) const
69 if(rComp
.getPolygonIndex() == getPolygonIndex())
71 return rComp
.getPointIndex() < getPointIndex();
74 return rComp
.getPolygonIndex() < getPolygonIndex();
78 B2DPolyPolygon
& o_rPolyPolygon
,
79 const OUString
& rSvgDStatement
,
80 bool bHandleRelativeNextPointCompatible
,
81 PointIndexSet
* pHelpPointIndexSet
)
83 o_rPolyPolygon
.clear();
84 const sal_Int32
nLen(rSvgDStatement
.getLength());
90 // skip initial whitespace
91 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
95 bool bRelative(false);
96 const sal_Unicode
aCurrChar(rSvgDStatement
[nPos
]);
98 if(o_rPolyPolygon
.count() && !aCurrPoly
.count() && !(aCurrChar
== 'm' || aCurrChar
== 'M'))
100 // we have a new sub-polygon starting, but without a 'moveto' command.
101 // this requires to add the current point as start point to the polygon
102 // (see SVG1.1 8.3.3 The "closepath" command)
103 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
111 // consume CurrChar and whitespace
113 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
115 // create closed polygon and reset import values
116 if(aCurrPoly
.count())
118 if(!bHandleRelativeNextPointCompatible
)
120 // SVG defines that "the next subpath starts at the
121 // same initial point as the current subpath", so set the
122 // current point if we do not need to be compatible
123 nLastX
= aCurrPoly
.getB2DPoint(0).getX();
124 nLastY
= aCurrPoly
.getB2DPoint(0).getY();
127 aCurrPoly
.setClosed(true);
128 o_rPolyPolygon
.append(aCurrPoly
);
138 // create non-closed polygon and reset import values
139 if(aCurrPoly
.count())
141 o_rPolyPolygon
.append(aCurrPoly
);
144 [[fallthrough
]]; // to add coordinate data as 1st point of new polygon
149 if(aCurrChar
== 'm' || aCurrChar
== 'l')
154 // consume CurrChar and whitespace
156 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
158 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
162 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
163 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
176 aCurrPoly
.append(B2DPoint(nX
, nY
));
189 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
191 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
193 double nX
, nY(nLastY
);
195 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
206 aCurrPoly
.append(B2DPoint(nX
, nY
));
219 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
221 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
223 double nX(nLastX
), nY
;
225 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
236 aCurrPoly
.append(B2DPoint(nX
, nY
));
249 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
251 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
256 if(!basegfx::internal::importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
257 if(!basegfx::internal::importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
258 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
259 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
269 // ensure existence of start point
270 if(!aCurrPoly
.count())
272 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
275 // get first control point. It's the reflection of the PrevControlPoint
276 // of the last point. If not existent, use current point (see SVG)
277 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
278 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
280 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
282 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
283 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
285 // use mirrored previous control point
286 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
287 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
290 // append curved edge
291 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
308 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
310 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
316 if(!basegfx::internal::importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
317 if(!basegfx::internal::importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
318 if(!basegfx::internal::importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
319 if(!basegfx::internal::importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
320 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
321 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
333 // ensure existence of start point
334 if(!aCurrPoly
.count())
336 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
339 // append curved edge
340 aCurrPoly
.appendBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
349 // #100617# quadratic beziers are imported as cubic ones
358 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
360 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
365 if(!basegfx::internal::importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
366 if(!basegfx::internal::importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
367 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
368 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
378 // ensure existence of start point
379 if(!aCurrPoly
.count())
381 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
384 // append curved edge
385 aCurrPoly
.appendQuadraticBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX
, nY
));
394 // #100617# relative quadratic beziers are imported as cubic
403 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
405 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
409 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
410 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
418 // ensure existence of start point
419 if(!aCurrPoly
.count())
421 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
424 // get first control point. It's the reflection of the PrevControlPoint
425 // of the last point. If not existent, use current point (see SVG)
426 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
427 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
428 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
430 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
432 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
434 // use mirrored previous control point
435 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
436 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
439 if(!aPrevControl
.equal(aPrevPoint
))
441 // there is a prev control point, and we have the already mirrored one
442 // in aPrevControl. We also need the quadratic control point for this
443 // new quadratic segment to calculate the 2nd cubic control point
444 const B2DPoint
aQuadControlPoint(
445 ((3.0 * aPrevControl
.getX()) - aPrevPoint
.getX()) / 2.0,
446 ((3.0 * aPrevControl
.getY()) - aPrevPoint
.getY()) / 2.0);
448 // calculate the cubic bezier coefficients from the quadratic ones.
449 const double nX2Prime((aQuadControlPoint
.getX() * 2.0 + nX
) / 3.0);
450 const double nY2Prime((aQuadControlPoint
.getY() * 2.0 + nY
) / 3.0);
452 // append curved edge, use mirrored cubic control point directly
453 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
457 // when no previous control, SVG says to use current point -> straight line.
458 // Just add end point
459 aCurrPoly
.append(B2DPoint(nX
, nY
));
477 basegfx::internal::skipSpaces(nPos
, rSvgDStatement
, nLen
);
479 while(nPos
< nLen
&& basegfx::internal::isOnNumberChar(rSvgDStatement
, nPos
))
482 double fRX
, fRY
, fPhi
;
483 sal_Int32 bLargeArcFlag
, bSweepFlag
;
485 if(!basegfx::internal::importDoubleAndSpaces(fRX
, nPos
, rSvgDStatement
, nLen
)) return false;
486 if(!basegfx::internal::importDoubleAndSpaces(fRY
, nPos
, rSvgDStatement
, nLen
)) return false;
487 if(!basegfx::internal::importDoubleAndSpaces(fPhi
, nPos
, rSvgDStatement
, nLen
)) return false;
488 if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
489 if(!basegfx::internal::importFlagAndSpaces(bSweepFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
490 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
491 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
499 if( rtl::math::approxEqual(nX
, nLastX
) && rtl::math::approxEqual(nY
, nLastY
) )
500 continue; // start==end -> skip according to SVG spec
502 if( fRX
== 0.0 || fRY
== 0.0 )
504 // straight line segment according to SVG spec
505 aCurrPoly
.append(B2DPoint(nX
, nY
));
509 // normalize according to SVG spec
510 fRX
=fabs(fRX
); fRY
=fabs(fRY
);
512 // from the SVG spec, appendix F.6.4
514 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
515 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
516 const B2DPoint
p1(nLastX
, nLastY
);
517 const B2DPoint
p2(nX
, nY
);
518 B2DHomMatrix
aTransform(basegfx::utils::createRotateB2DHomMatrix(
521 const B2DPoint
p1_prime( aTransform
* B2DPoint(((p1
-p2
)/2.0)) );
523 // ______________________________________ rx y1'
524 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
525 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
527 // chose + if f_A != f_S
528 // chose - if f_A = f_S
529 B2DPoint aCenter_prime
;
530 const double fRadicant(
531 (fRX
*fRX
*fRY
*fRY
- fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() - fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX())/
532 (fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() + fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX()));
533 if( fRadicant
< 0.0 )
535 // no solution - according to SVG
536 // spec, scale up ellipse
537 // uniformly such that it passes
538 // through end points (denominator
539 // of radicant solved for fRY,
541 const double fRatio(fRX
/fRY
);
542 const double fRadicant2(
543 p1_prime
.getY()*p1_prime
.getY() +
544 p1_prime
.getX()*p1_prime
.getX()/(fRatio
*fRatio
));
545 if( fRadicant2
< 0.0 )
547 // only trivial solution, one
548 // of the axes 0 -> straight
549 // line segment according to
551 aCurrPoly
.append(B2DPoint(nX
, nY
));
555 fRY
=sqrt(fRadicant2
);
558 // keep center_prime forced to (0,0)
562 const double fFactor(
563 (bLargeArcFlag
==bSweepFlag
? -1.0 : 1.0) *
566 // actually calculate center_prime
567 aCenter_prime
= B2DPoint(
568 fFactor
*fRX
*p1_prime
.getY()/fRY
,
569 -fFactor
*fRY
*p1_prime
.getX()/fRX
);
573 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
576 // 1 | (x1' - cx')/rx |
577 // theta1 = angle(( ), | | )
578 // 0 | (y1' - cy')/ry |
579 const B2DPoint
aRadii(fRX
,fRY
);
581 B2DVector(1.0,0.0).angle(
582 (p1_prime
-aCenter_prime
)/aRadii
));
584 // |1| | (-x1' - cx')/rx |
585 // theta2 = angle( | | , | | )
586 // |0| | (-y1' - cy')/ry |
588 B2DVector(1.0,0.0).angle(
589 (-p1_prime
-aCenter_prime
)/aRadii
));
591 // map both angles to [0,2pi)
592 fTheta1
= fmod(2*M_PI
+fTheta1
,2*M_PI
);
593 fTheta2
= fmod(2*M_PI
+fTheta2
,2*M_PI
);
595 // make sure the large arc is taken
597 // createPolygonFromEllipseSegment()
598 // normalizes to e.g. cw arc)
600 std::swap(fTheta1
,fTheta2
);
602 // finally, create bezier polygon from this
604 utils::createPolygonFromUnitEllipseSegment(
607 // transform ellipse by rotation & move to final center
608 aTransform
= basegfx::utils::createScaleB2DHomMatrix(fRX
, fRY
);
609 aTransform
.translate(aCenter_prime
.getX(),
610 aCenter_prime
.getY());
611 aTransform
.rotate(deg2rad(fPhi
));
612 const B2DPoint
aOffset((p1
+p2
)/2.0);
613 aTransform
.translate(aOffset
.getX(),
615 aSegment
.transform(aTransform
);
617 // createPolygonFromEllipseSegment()
618 // always creates arcs that are
619 // positively oriented - flip polygon
620 // if we swapped angles above
624 // remember PointIndex of evtl. added pure helper points
625 sal_uInt32
nPointIndex(aCurrPoly
.count() + 1);
626 aCurrPoly
.append(aSegment
);
628 // if asked for, mark pure helper points by adding them to the index list of
630 if(pHelpPointIndexSet
&& aCurrPoly
.count() > 1)
632 const sal_uInt32
nPolyIndex(o_rPolyPolygon
.count());
634 for(;nPointIndex
+ 1 < aCurrPoly
.count(); nPointIndex
++)
636 pHelpPointIndexSet
->insert(PointIndex(nPolyIndex
, nPointIndex
));
650 SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
659 // if there is polygon data, create non-closed polygon
660 if(aCurrPoly
.count())
662 o_rPolyPolygon
.append(aCurrPoly
);
668 bool importFromSvgPoints( B2DPolygon
& o_rPoly
,
669 const OUString
& rSvgPointsAttribute
)
672 const sal_Int32
nLen(rSvgPointsAttribute
.getLength());
676 // skip initial whitespace
677 basegfx::internal::skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
681 if(!basegfx::internal::importDoubleAndSpaces(nX
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
682 if(!basegfx::internal::importDoubleAndSpaces(nY
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
685 o_rPoly
.append(B2DPoint(nX
, nY
));
687 // skip to next number, or finish
688 basegfx::internal::skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
694 OUString
exportToSvgPoints( const B2DPolygon
& rPoly
)
696 SAL_WARN_IF(rPoly
.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
697 const sal_uInt32
nPointCount(rPoly
.count());
698 OUStringBuffer aResult
;
700 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
702 const basegfx::B2DPoint
aPoint(rPoly
.getB2DPoint(a
));
709 aResult
.append(aPoint
.getX());
711 aResult
.append(aPoint
.getY());
714 return aResult
.makeStringAndClear();
717 OUString
exportToSvgD(
718 const B2DPolyPolygon
& rPolyPolygon
,
719 bool bUseRelativeCoordinates
,
720 bool bDetectQuadraticBeziers
,
721 bool bHandleRelativeNextPointCompatible
,
722 bool bOOXMLMotionPath
)
724 const sal_uInt32
nCount(rPolyPolygon
.count());
725 sal_uInt32 nCombinedPointCount
= 0;
726 for(sal_uInt32
i(0); i
< nCount
; i
++)
728 const B2DPolygon
& aPolygon(rPolyPolygon
.getB2DPolygon(i
));
729 nCombinedPointCount
+= aPolygon
.count();
732 OUStringBuffer
aResult(std::max
<int>(nCombinedPointCount
* 32,512));
733 B2DPoint
aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
735 for(sal_uInt32
i(0); i
< nCount
; i
++)
737 const B2DPolygon
& aPolygon(rPolyPolygon
.getB2DPolygon(i
));
738 const sal_uInt32
nPointCount(aPolygon
.count());
742 const bool bPolyUsesControlPoints(aPolygon
.areControlPointsUsed());
743 const sal_uInt32
nEdgeCount(aPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
744 sal_Unicode
aLastSVGCommand(' '); // last SVG command char
745 B2DPoint aLeft
, aRight
; // for quadratic bezier test
747 // handle polygon start point
748 B2DPoint
aEdgeStart(aPolygon
.getB2DPoint(0));
749 bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates
);
751 if(bHandleRelativeNextPointCompatible
)
753 // To get around the error that the start point for the next polygon is the
754 // start point of the current one (and not the last as it was handled up to now)
755 // do force to write an absolute 'M' command as start for the next polygon
756 bUseRelativeCoordinatesForFirstPoint
= false;
759 // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
760 putCommandChar(aResult
, aLastSVGCommand
, 'M', bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
761 putNumberChar(aResult
, aEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
762 putNumberChar(aResult
, aEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinatesForFirstPoint
, bOOXMLMotionPath
);
763 aLastSVGCommand
= bUseRelativeCoordinatesForFirstPoint
? 'l' : 'L';
764 aCurrentSVGPosition
= aEdgeStart
;
766 for(sal_uInt32
nIndex(0); nIndex
< nEdgeCount
; nIndex
++)
768 // prepare access to next point
769 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
770 const B2DPoint
aEdgeEnd(aPolygon
.getB2DPoint(nNextIndex
));
772 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
773 const bool bEdgeIsBezier(bPolyUsesControlPoints
774 && (aPolygon
.isNextControlPointUsed(nIndex
) || aPolygon
.isPrevControlPointUsed(nNextIndex
)));
778 // handle bezier edge
779 const B2DPoint
aControlEdgeStart(aPolygon
.getNextControlPoint(nIndex
));
780 const B2DPoint
aControlEdgeEnd(aPolygon
.getPrevControlPoint(nNextIndex
));
781 bool bIsQuadraticBezier(false);
783 // check continuity at current edge's start point. For SVG, do NOT use an
784 // existing continuity since no 'S' or 's' statement should be written. At
785 // import, that 'previous' control vector is not available. SVG documentation
786 // says for interpretation:
788 // "(If there is no previous command or if the previous command was
789 // not a C, c, S or s, assume the first control point is coincident
790 // with the current point.)"
792 // That's what is done from our import, so avoid exporting it as first statement
794 const bool bSymmetricAtEdgeStart(
795 !bOOXMLMotionPath
&& nIndex
!= 0
796 && aPolygon
.getContinuityInPoint(nIndex
) == B2VectorContinuity::C2
);
798 if(bDetectQuadraticBeziers
)
800 // check for quadratic beziers - that's
801 // the case if both control points are in
802 // the same place when they are prolonged
803 // to the common quadratic control point
805 // Left: P = (3P1 - P0) / 2
806 // Right: P = (3P2 - P3) / 2
807 aLeft
= B2DPoint((3.0 * aControlEdgeStart
- aEdgeStart
) / 2.0);
808 aRight
= B2DPoint((3.0 * aControlEdgeEnd
- aEdgeEnd
) / 2.0);
809 bIsQuadraticBezier
= aLeft
.equal(aRight
);
812 if(bIsQuadraticBezier
)
814 // approximately equal, export as quadratic bezier
815 if(bSymmetricAtEdgeStart
)
817 putCommandChar(aResult
, aLastSVGCommand
, 'T', bUseRelativeCoordinates
, bOOXMLMotionPath
);
819 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
820 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
821 aCurrentSVGPosition
= aEdgeEnd
;
825 putCommandChar(aResult
, aLastSVGCommand
, 'Q', bUseRelativeCoordinates
, bOOXMLMotionPath
);
827 putNumberChar(aResult
, aLeft
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
828 putNumberChar(aResult
, aLeft
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
829 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
830 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
831 aCurrentSVGPosition
= aEdgeEnd
;
836 // export as cubic bezier
837 if(bSymmetricAtEdgeStart
)
839 putCommandChar(aResult
, aLastSVGCommand
, 'S', bUseRelativeCoordinates
, bOOXMLMotionPath
);
841 putNumberChar(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
842 putNumberChar(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
843 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
844 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
845 aCurrentSVGPosition
= aEdgeEnd
;
849 putCommandChar(aResult
, aLastSVGCommand
, 'C', bUseRelativeCoordinates
, bOOXMLMotionPath
);
851 putNumberChar(aResult
, aControlEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
852 putNumberChar(aResult
, aControlEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
853 putNumberChar(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
854 putNumberChar(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
855 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
856 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
857 aCurrentSVGPosition
= aEdgeEnd
;
866 // it's a closed polygon's last edge and it's not a bezier edge, so there is
867 // no need to write it
871 const bool bXEqual(rtl::math::approxEqual(aEdgeStart
.getX(), aEdgeEnd
.getX()));
872 const bool bYEqual(rtl::math::approxEqual(aEdgeStart
.getY(), aEdgeEnd
.getY()));
874 if(bXEqual
&& bYEqual
)
876 // point is a double point; do not export at all
878 else if(bXEqual
&& !bOOXMLMotionPath
)
880 // export as vertical line
881 putCommandChar(aResult
, aLastSVGCommand
, 'V', bUseRelativeCoordinates
, bOOXMLMotionPath
);
883 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
884 aCurrentSVGPosition
= aEdgeEnd
;
886 else if(bYEqual
&& !bOOXMLMotionPath
)
888 // export as horizontal line
889 putCommandChar(aResult
, aLastSVGCommand
, 'H', bUseRelativeCoordinates
, bOOXMLMotionPath
);
891 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
892 aCurrentSVGPosition
= aEdgeEnd
;
897 putCommandChar(aResult
, aLastSVGCommand
, 'L', bUseRelativeCoordinates
, bOOXMLMotionPath
);
899 putNumberChar(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
900 putNumberChar(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
, bOOXMLMotionPath
);
901 aCurrentSVGPosition
= aEdgeEnd
;
906 // prepare edge start for next loop step
907 aEdgeStart
= aEdgeEnd
;
910 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
911 if(aPolygon
.isClosed())
913 putCommandChar(aResult
, aLastSVGCommand
, 'Z', bUseRelativeCoordinates
, bOOXMLMotionPath
);
915 else if (bOOXMLMotionPath
)
917 putCommandChar(aResult
, aLastSVGCommand
, 'E', bUseRelativeCoordinates
, bOOXMLMotionPath
);
920 if(!bHandleRelativeNextPointCompatible
)
922 // SVG defines that "the next subpath starts at the same initial point as the current subpath",
923 // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
924 aCurrentSVGPosition
= aPolygon
.getB2DPoint(0);
929 return aResult
.makeStringAndClear();
934 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */