1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: b2dsvgpolypolygon.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_basegfx.hxx"
34 #include <basegfx/polygon/b2dpolygontools.hxx>
35 #include <basegfx/polygon/b2dpolypolygontools.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
37 #include <basegfx/polygon/b2dpolypolygon.hxx>
38 #include <basegfx/matrix/b2dhommatrix.hxx>
39 #include <rtl/ustring.hxx>
40 #include <rtl/math.hxx>
48 void lcl_skipSpaces(sal_Int32
& io_rPos
,
49 const ::rtl::OUString
& rStr
,
52 while( io_rPos
< nLen
&&
53 sal_Unicode(' ') == rStr
[io_rPos
] )
59 void lcl_skipSpacesAndCommas(sal_Int32
& io_rPos
,
60 const ::rtl::OUString
& rStr
,
64 && (sal_Unicode(' ') == rStr
[io_rPos
] || sal_Unicode(',') == rStr
[io_rPos
]))
70 inline bool lcl_isOnNumberChar(const sal_Unicode aChar
, bool bSignAllowed
= true)
72 const bool bPredicate( (sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
73 || (bSignAllowed
&& sal_Unicode('+') == aChar
)
74 || (bSignAllowed
&& sal_Unicode('-') == aChar
) );
79 inline bool lcl_isOnNumberChar(const ::rtl::OUString
& rStr
, const sal_Int32 nPos
, bool bSignAllowed
= true)
81 return lcl_isOnNumberChar(rStr
[nPos
],
85 bool lcl_getDoubleChar(double& o_fRetval
,
87 const ::rtl::OUString
& rStr
,
88 const sal_Int32
/*nLen*/)
90 sal_Unicode
aChar( rStr
[io_rPos
] );
91 ::rtl::OUStringBuffer sNumberString
;
93 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
95 sNumberString
.append(rStr
[io_rPos
]);
96 aChar
= rStr
[++io_rPos
];
99 while((sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
100 || sal_Unicode('.') == aChar
)
102 sNumberString
.append(rStr
[io_rPos
]);
103 aChar
= rStr
[++io_rPos
];
106 if(sal_Unicode('e') == aChar
|| sal_Unicode('E') == aChar
)
108 sNumberString
.append(rStr
[io_rPos
]);
109 aChar
= rStr
[++io_rPos
];
111 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
113 sNumberString
.append(rStr
[io_rPos
]);
114 aChar
= rStr
[++io_rPos
];
117 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
119 sNumberString
.append(rStr
[io_rPos
]);
120 aChar
= rStr
[++io_rPos
];
124 if(sNumberString
.getLength())
126 rtl_math_ConversionStatus eStatus
;
127 o_fRetval
= ::rtl::math::stringToDouble( sNumberString
.makeStringAndClear(),
132 return ( eStatus
== rtl_math_ConversionStatus_Ok
);
138 bool lcl_importDoubleAndSpaces( double& o_fRetval
,
140 const ::rtl::OUString
& rStr
,
141 const sal_Int32 nLen
)
143 if( !lcl_getDoubleChar(o_fRetval
, io_rPos
, rStr
, nLen
) )
146 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
151 bool lcl_importNumberAndSpaces(sal_Int32
& o_nRetval
,
153 const ::rtl::OUString
& rStr
,
154 const sal_Int32 nLen
)
156 sal_Unicode
aChar( rStr
[io_rPos
] );
157 ::rtl::OUStringBuffer sNumberString
;
159 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
161 sNumberString
.append(rStr
[io_rPos
]);
162 aChar
= rStr
[++io_rPos
];
165 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
167 sNumberString
.append(rStr
[io_rPos
]);
168 aChar
= rStr
[++io_rPos
];
171 if(sNumberString
.getLength())
173 o_nRetval
= sNumberString
.makeStringAndClear().toInt32();
174 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
182 void lcl_skipNumber(sal_Int32
& io_rPos
,
183 const ::rtl::OUString
& rStr
,
184 const sal_Int32 nLen
)
186 bool bSignAllowed(true);
188 while(io_rPos
< nLen
&& lcl_isOnNumberChar(rStr
, io_rPos
, bSignAllowed
))
190 bSignAllowed
= false;
195 void lcl_skipDouble(sal_Int32
& io_rPos
,
196 const ::rtl::OUString
& rStr
,
197 const sal_Int32
/*nLen*/)
199 sal_Unicode
aChar( rStr
[io_rPos
] );
201 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
202 aChar
= rStr
[++io_rPos
];
204 while((sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
205 || sal_Unicode('.') == aChar
)
207 aChar
= rStr
[++io_rPos
];
210 if(sal_Unicode('e') == aChar
|| sal_Unicode('E') == aChar
)
212 aChar
= rStr
[++io_rPos
];
214 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
215 aChar
= rStr
[++io_rPos
];
217 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
219 aChar
= rStr
[++io_rPos
];
223 void lcl_skipNumberAndSpacesAndCommas(sal_Int32
& io_rPos
,
224 const ::rtl::OUString
& rStr
,
225 const sal_Int32 nLen
)
227 lcl_skipNumber(io_rPos
, rStr
, nLen
);
228 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
231 // #100617# Allow to skip doubles, too.
232 void lcl_skipDoubleAndSpacesAndCommas(sal_Int32
& io_rPos
,
233 const ::rtl::OUString
& rStr
,
234 const sal_Int32 nLen
)
236 lcl_skipDouble(io_rPos
, rStr
, nLen
);
237 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
240 void lcl_putNumberChar( ::rtl::OUStringBuffer
& rStr
,
243 rStr
.append( fValue
);
246 void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer
& rStr
,
249 bool bUseRelativeCoordinates
)
251 if( bUseRelativeCoordinates
)
254 const sal_Int32
aLen( rStr
.getLength() );
257 if( lcl_isOnNumberChar(rStr
.charAt(aLen
- 1), false) &&
260 rStr
.append( sal_Unicode(' ') );
264 lcl_putNumberChar(rStr
, fValue
);
267 inline sal_Unicode
lcl_getCommand( sal_Char cUpperCaseCommand
,
268 sal_Char cLowerCaseCommand
,
269 bool bUseRelativeCoordinates
)
271 return bUseRelativeCoordinates
? cLowerCaseCommand
: cUpperCaseCommand
;
275 bool importFromSvgD(B2DPolyPolygon
& o_rPolyPolygon
, const ::rtl::OUString
& rSvgDStatement
)
277 o_rPolyPolygon
.clear();
278 const sal_Int32
nLen(rSvgDStatement
.getLength());
280 bool bIsClosed(false);
281 double nLastX( 0.0 );
282 double nLastY( 0.0 );
283 B2DPolygon aCurrPoly
;
285 // skip initial whitespace
286 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
290 bool bRelative(false);
292 const sal_Unicode
aCurrChar(rSvgDStatement
[nPos
]);
300 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
302 // remember closed state of current polygon
311 // FALLTHROUGH intended
316 if('m' == aCurrChar
|| 'l' == aCurrChar
)
323 // new polygon start, finish old one
324 if(aCurrPoly
.count())
326 // add current polygon
329 closeWithGeometryChange(aCurrPoly
);
332 o_rPolyPolygon
.append(aCurrPoly
);
334 // reset import values
341 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
343 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
347 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
348 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
361 aCurrPoly
.append(B2DPoint(nX
, nY
));
369 // FALLTHROUGH intended
374 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
376 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
378 double nX
, nY(nLastY
);
380 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
391 aCurrPoly
.append(B2DPoint(nX
, nY
));
399 // FALLTHROUGH intended
404 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
406 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
408 double nX(nLastX
), nY
;
410 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
421 aCurrPoly
.append(B2DPoint(nX
, nY
));
429 // FALLTHROUGH intended
434 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
436 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
441 if(!lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
442 if(!lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
443 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
444 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
454 // ensure existance of start point
455 if(!aCurrPoly
.count())
457 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
460 // get first control point. It's the reflection of the PrevControlPoint
461 // of the last point. If not existent, use current point (see SVG)
462 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
463 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
465 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
467 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
468 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
470 // use mirrored previous control point
471 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
472 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
475 // append curved edge
476 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
488 // FALLTHROUGH intended
493 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
495 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
501 if(!lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
502 if(!lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
503 if(!lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
504 if(!lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
505 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
506 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
518 // ensure existance of start point
519 if(!aCurrPoly
.count())
521 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
524 // append curved edge
525 aCurrPoly
.appendBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
534 // #100617# quadratic beziers are imported as cubic ones
538 // FALLTHROUGH intended
543 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
545 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
550 if(!lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
551 if(!lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
552 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
553 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
563 // calculate the cubic bezier coefficients from the quadratic ones
564 const double nX1Prime((nX1
* 2.0 + nLastX
) / 3.0);
565 const double nY1Prime((nY1
* 2.0 + nLastY
) / 3.0);
566 const double nX2Prime((nX1
* 2.0 + nX
) / 3.0);
567 const double nY2Prime((nY1
* 2.0 + nY
) / 3.0);
569 // ensure existance of start point
570 if(!aCurrPoly
.count())
572 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
575 // append curved edge
576 aCurrPoly
.appendBezierSegment(B2DPoint(nX1Prime
, nY1Prime
), B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
585 // #100617# relative quadratic beziers are imported as cubic
589 // FALLTHROUGH intended
594 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
596 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
600 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
601 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
609 // ensure existance of start point
610 if(!aCurrPoly
.count())
612 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
615 // get first control point. It's the reflection of the PrevControlPoint
616 // of the last point. If not existent, use current point (see SVG)
617 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
618 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
619 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
621 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
623 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
625 // use mirrored previous control point
626 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
627 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
630 if(!aPrevControl
.equal(aPrevPoint
))
632 // there is a prev control point, and we have the already mirrored one
633 // in aPrevControl. We also need the quadratic control point for this
634 // new quadratic segment to calculate the 2nd cubic control point
635 const B2DPoint
aQuadControlPoint(
636 ((3.0 * aPrevControl
.getX()) - aPrevPoint
.getX()) / 2.0,
637 ((3.0 * aPrevControl
.getY()) - aPrevPoint
.getY()) / 2.0);
639 // calculate the cubic bezier coefficients from the quadratic ones.
640 const double nX2Prime((aQuadControlPoint
.getX() * 2.0 + nX
) / 3.0);
641 const double nY2Prime((aQuadControlPoint
.getY() * 2.0 + nY
) / 3.0);
643 // append curved edge, use mirrored cubic control point directly
644 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
648 // when no previous control, SVG says to use current point -> straight line.
649 // Just add end point
650 aCurrPoly
.append(B2DPoint(nX
, nY
));
663 // FALLTHROUGH intended
668 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
670 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
673 double fRX
, fRY
, fPhi
;
674 sal_Int32 bLargeArcFlag
, bSweepFlag
;
676 if(!lcl_importDoubleAndSpaces(fRX
, nPos
, rSvgDStatement
, nLen
)) return false;
677 if(!lcl_importDoubleAndSpaces(fRY
, nPos
, rSvgDStatement
, nLen
)) return false;
678 if(!lcl_importDoubleAndSpaces(fPhi
, nPos
, rSvgDStatement
, nLen
)) return false;
679 if(!lcl_importNumberAndSpaces(bLargeArcFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
680 if(!lcl_importNumberAndSpaces(bSweepFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
681 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
682 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
690 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(aCurrPoly
.count() - 1));
692 if( nX
== nLastX
&& nY
== nLastY
)
693 continue; // start==end -> skip according to SVG spec
695 if( fRX
== 0.0 || fRY
== 0.0 )
697 // straight line segment according to SVG spec
698 aCurrPoly
.append(B2DPoint(nX
, nY
));
702 // normalize according to SVG spec
703 fRX
=fabs(fRX
); fRY
=fabs(fRY
);
705 // from the SVG spec, appendix F.6.4
707 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
708 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
709 const B2DPoint
p1(nLastX
, nLastY
);
710 const B2DPoint
p2(nX
, nY
);
711 B2DHomMatrix aTransform
; aTransform
.rotate(-fPhi
*M_PI
/180);
713 const B2DPoint
p1_prime( aTransform
* B2DPoint(((p1
-p2
)/2.0)) );
715 // ______________________________________ rx y1'
716 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
717 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
719 // chose + if f_A != f_S
720 // chose - if f_A = f_S
721 B2DPoint aCenter_prime
;
722 const double fRadicant(
723 (fRX
*fRX
*fRY
*fRY
- fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() - fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX())/
724 (fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() + fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX()));
725 if( fRadicant
< 0.0 )
727 // no solution - according to SVG
728 // spec, scale up ellipse
729 // uniformly such that it passes
730 // through end points (denominator
731 // of radicant solved for fRY,
733 const double fRatio(fRX
/fRY
);
734 const double fRadicant2(
735 p1_prime
.getY()*p1_prime
.getY() +
736 p1_prime
.getX()*p1_prime
.getX()/(fRatio
*fRatio
));
737 if( fRadicant2
< 0.0 )
739 // only trivial solution, one
740 // of the axes 0 -> straight
741 // line segment according to
743 aCurrPoly
.append(B2DPoint(nX
, nY
));
747 fRY
=sqrt(fRadicant2
);
750 // keep center_prime forced to (0,0)
754 const double fFactor(
755 (bLargeArcFlag
==bSweepFlag
? -1.0 : 1.0) *
758 // actually calculate center_prime
759 aCenter_prime
= B2DPoint(
760 fFactor
*fRX
*p1_prime
.getY()/fRY
,
761 -fFactor
*fRY
*p1_prime
.getX()/fRX
);
765 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
768 // 1 | (x1' - cx')/rx |
769 // theta1 = angle(( ), | | )
770 // 0 | (y1' - cy')/ry |
771 const B2DPoint
aRadii(fRX
,fRY
);
773 B2DVector(1.0,0.0).angle(
774 (p1_prime
-aCenter_prime
)/aRadii
));
776 // |1| | (-x1' - cx')/rx |
777 // theta2 = angle( | | , | | )
778 // |0| | (-y1' - cy')/ry |
780 B2DVector(1.0,0.0).angle(
781 (-p1_prime
-aCenter_prime
)/aRadii
));
783 // map both angles to [0,2pi)
784 fTheta1
= fmod(2*M_PI
+fTheta1
,2*M_PI
);
785 fTheta2
= fmod(2*M_PI
+fTheta2
,2*M_PI
);
787 // make sure the large arc is taken
789 // createPolygonFromEllipseSegment()
790 // normalizes to e.g. cw arc)
791 const bool bFlipSegment( (bLargeArcFlag
!=0) ==
792 (fmod(fTheta2
+2*M_PI
-fTheta1
,
795 std::swap(fTheta1
,fTheta2
);
797 // finally, create bezier polygon from this
799 tools::createPolygonFromUnitEllipseSegment(
802 // transform ellipse by rotation & move to final center
803 aTransform
.identity();
804 aTransform
.scale(fRX
,fRY
);
805 aTransform
.translate(aCenter_prime
.getX(),
806 aCenter_prime
.getY());
807 aTransform
.rotate(fPhi
*M_PI
/180);
808 const B2DPoint
aOffset((p1
+p2
)/2.0);
809 aTransform
.translate(aOffset
.getX(),
811 aSegment
.transform(aTransform
);
813 // createPolygonFromEllipseSegment()
814 // always creates arcs that are
815 // positively oriented - flip polygon
816 // if we swapped angles above
819 aCurrPoly
.append(aSegment
);
831 OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
832 OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar
);
839 if(aCurrPoly
.count())
841 // end-process last poly
844 closeWithGeometryChange(aCurrPoly
);
847 o_rPolyPolygon
.append(aCurrPoly
);
853 bool importFromSvgPoints( B2DPolygon
& o_rPoly
,
854 const ::rtl::OUString
& rSvgPointsAttribute
)
857 const sal_Int32
nLen(rSvgPointsAttribute
.getLength());
861 // skip initial whitespace
862 lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
866 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
867 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
870 o_rPoly
.append(B2DPoint(nX
, nY
));
872 // skip to next number, or finish
873 lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
879 ::rtl::OUString
exportToSvgD(
880 const B2DPolyPolygon
& rPolyPolygon
,
881 bool bUseRelativeCoordinates
,
882 bool bDetectQuadraticBeziers
)
884 const sal_uInt32
nCount(rPolyPolygon
.count());
885 ::rtl::OUStringBuffer aResult
;
886 B2DPoint
aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
888 for(sal_uInt32
i(0); i
< nCount
; i
++)
890 const B2DPolygon
aPolygon(rPolyPolygon
.getB2DPolygon(i
));
891 const sal_uInt32
nPointCount(aPolygon
.count());
895 const bool bPolyUsesControlPoints(aPolygon
.areControlPointsUsed());
896 const sal_uInt32
nEdgeCount(aPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
897 sal_Unicode
aLastSVGCommand(' '); // last SVG command char
898 B2DPoint aLeft
, aRight
; // for quadratic bezier test
900 // handle polygon start point
901 B2DPoint
aEdgeStart(aPolygon
.getB2DPoint(0));
902 aResult
.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates
));
903 lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
904 lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
905 aLastSVGCommand
= lcl_getCommand('L', 'l', bUseRelativeCoordinates
);
906 aCurrentSVGPosition
= aEdgeStart
;
908 for(sal_uInt32
nIndex(0); nIndex
< nEdgeCount
; nIndex
++)
910 // prepare access to next point
911 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
912 const B2DPoint
aEdgeEnd(aPolygon
.getB2DPoint(nNextIndex
));
914 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
915 const bool bEdgeIsBezier(bPolyUsesControlPoints
916 && (aPolygon
.isNextControlPointUsed(nIndex
) || aPolygon
.isPrevControlPointUsed(nNextIndex
)));
920 // handle bezier edge
921 const B2DPoint
aControlEdgeStart(aPolygon
.getNextControlPoint(nIndex
));
922 const B2DPoint
aControlEdgeEnd(aPolygon
.getPrevControlPoint(nNextIndex
));
923 bool bIsQuadraticBezier(false);
925 // check continuity at current edge's start point. For SVG, do NOT use an
926 // existing continuity since no 'S' or 's' statement should be written. At
927 // import, that 'previous' control vector is not available. SVG documentation
928 // says for interpretation:
930 // "(If there is no previous command or if the previous command was
931 // not an C, c, S or s, assume the first control point is coincident
932 // with the current point.)"
934 // That's what is done from our import, so avoid exporting it as first statement
936 const bool bSymmetricAtEdgeStart(
938 && CONTINUITY_C2
== aPolygon
.getContinuityInPoint(nIndex
));
940 if(bDetectQuadraticBeziers
)
942 // check for quadratic beziers - that's
943 // the case if both control points are in
944 // the same place when they are prolonged
945 // to the common quadratic control point
947 // Left: P = (3P1 - P0) / 2
948 // Right: P = (3P2 - P3) / 2
949 aLeft
= B2DPoint((3.0 * aControlEdgeStart
- aEdgeStart
) / 2.0);
950 aRight
= B2DPoint((3.0 * aControlEdgeEnd
- aEdgeEnd
) / 2.0);
951 bIsQuadraticBezier
= aLeft
.equal(aRight
);
954 if(bIsQuadraticBezier
)
956 // approximately equal, export as quadratic bezier
957 if(bSymmetricAtEdgeStart
)
959 const sal_Unicode
aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates
));
961 if(aLastSVGCommand
!= aCommand
)
963 aResult
.append(aCommand
);
964 aLastSVGCommand
= aCommand
;
967 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
968 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
969 aLastSVGCommand
= aCommand
;
970 aCurrentSVGPosition
= aEdgeEnd
;
974 const sal_Unicode
aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates
));
976 if(aLastSVGCommand
!= aCommand
)
978 aResult
.append(aCommand
);
979 aLastSVGCommand
= aCommand
;
982 lcl_putNumberCharWithSpace(aResult
, aLeft
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
983 lcl_putNumberCharWithSpace(aResult
, aLeft
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
984 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
985 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
986 aLastSVGCommand
= aCommand
;
987 aCurrentSVGPosition
= aEdgeEnd
;
992 // export as cubic bezier
993 if(bSymmetricAtEdgeStart
)
995 const sal_Unicode
aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates
));
997 if(aLastSVGCommand
!= aCommand
)
999 aResult
.append(aCommand
);
1000 aLastSVGCommand
= aCommand
;
1003 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1004 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1005 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1006 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1007 aLastSVGCommand
= aCommand
;
1008 aCurrentSVGPosition
= aEdgeEnd
;
1012 const sal_Unicode
aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates
));
1014 if(aLastSVGCommand
!= aCommand
)
1016 aResult
.append(aCommand
);
1017 aLastSVGCommand
= aCommand
;
1020 lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1021 lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1022 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1023 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1024 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1025 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1026 aLastSVGCommand
= aCommand
;
1027 aCurrentSVGPosition
= aEdgeEnd
;
1036 // it's a closed polygon's last edge and it's not a bezier edge, so there is
1037 // no need to write it
1041 const bool bXEqual(aEdgeStart
.getX() == aEdgeEnd
.getX());
1042 const bool bYEqual(aEdgeStart
.getY() == aEdgeEnd
.getY());
1044 if(bXEqual
&& bYEqual
)
1046 // point is a double point; do not export at all
1050 // export as vertical line
1051 const sal_Unicode
aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates
));
1053 if(aLastSVGCommand
!= aCommand
)
1055 aResult
.append(aCommand
);
1056 aLastSVGCommand
= aCommand
;
1059 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1060 aCurrentSVGPosition
= aEdgeEnd
;
1064 // export as horizontal line
1065 const sal_Unicode
aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates
));
1067 if(aLastSVGCommand
!= aCommand
)
1069 aResult
.append(aCommand
);
1070 aLastSVGCommand
= aCommand
;
1073 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1074 aCurrentSVGPosition
= aEdgeEnd
;
1079 const sal_Unicode
aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates
));
1081 if(aLastSVGCommand
!= aCommand
)
1083 aResult
.append(aCommand
);
1084 aLastSVGCommand
= aCommand
;
1087 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1088 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1089 aCurrentSVGPosition
= aEdgeEnd
;
1094 // prepare edge start for next loop step
1095 aEdgeStart
= aEdgeEnd
;
1098 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1099 if(aPolygon
.isClosed())
1101 aResult
.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates
));
1106 return aResult
.makeStringAndClear();