1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2000, 2010 Oracle and/or its affiliates.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
11 * OpenOffice.org is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
15 * OpenOffice.org is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details
19 * (a copy is included in the LICENSE file that accompanied this code).
21 * You should have received a copy of the GNU Lesser General Public License
22 * version 3 along with OpenOffice.org. If not, see
23 * <http://www.openoffice.org/license.html>
24 * for a copy of the LGPLv3 License.
26 ************************************************************************/
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_basegfx.hxx"
31 #include <basegfx/polygon/b2dpolygontools.hxx>
32 #include <basegfx/polygon/b2dpolypolygontools.hxx>
33 #include <basegfx/polygon/b2dpolygontools.hxx>
34 #include <basegfx/polygon/b2dpolypolygon.hxx>
35 #include <basegfx/matrix/b2dhommatrix.hxx>
36 #include <basegfx/matrix/b2dhommatrixtools.hxx>
37 #include <rtl/ustring.hxx>
38 #include <rtl/math.hxx>
46 void lcl_skipSpaces(sal_Int32
& io_rPos
,
47 const ::rtl::OUString
& rStr
,
50 while( io_rPos
< nLen
&&
51 sal_Unicode(' ') == rStr
[io_rPos
] )
57 void lcl_skipSpacesAndCommas(sal_Int32
& io_rPos
,
58 const ::rtl::OUString
& rStr
,
62 && (sal_Unicode(' ') == rStr
[io_rPos
] || sal_Unicode(',') == rStr
[io_rPos
]))
68 inline bool lcl_isOnNumberChar(const sal_Unicode aChar
, bool bSignAllowed
= true)
70 const bool bPredicate( (sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
71 || (bSignAllowed
&& sal_Unicode('+') == aChar
)
72 || (bSignAllowed
&& sal_Unicode('-') == aChar
) );
77 inline bool lcl_isOnNumberChar(const ::rtl::OUString
& rStr
, const sal_Int32 nPos
, bool bSignAllowed
= true)
79 return lcl_isOnNumberChar(rStr
[nPos
],
83 bool lcl_getDoubleChar(double& o_fRetval
,
85 const ::rtl::OUString
& rStr
,
86 const sal_Int32
/*nLen*/)
88 sal_Unicode
aChar( rStr
[io_rPos
] );
89 ::rtl::OUStringBuffer sNumberString
;
91 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
93 sNumberString
.append(rStr
[io_rPos
]);
94 aChar
= rStr
[++io_rPos
];
97 while((sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
98 || sal_Unicode('.') == aChar
)
100 sNumberString
.append(rStr
[io_rPos
]);
101 aChar
= rStr
[++io_rPos
];
104 if(sal_Unicode('e') == aChar
|| sal_Unicode('E') == aChar
)
106 sNumberString
.append(rStr
[io_rPos
]);
107 aChar
= rStr
[++io_rPos
];
109 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
111 sNumberString
.append(rStr
[io_rPos
]);
112 aChar
= rStr
[++io_rPos
];
115 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
117 sNumberString
.append(rStr
[io_rPos
]);
118 aChar
= rStr
[++io_rPos
];
122 if(sNumberString
.getLength())
124 rtl_math_ConversionStatus eStatus
;
125 o_fRetval
= ::rtl::math::stringToDouble( sNumberString
.makeStringAndClear(),
130 return ( eStatus
== rtl_math_ConversionStatus_Ok
);
136 bool lcl_importDoubleAndSpaces( double& o_fRetval
,
138 const ::rtl::OUString
& rStr
,
139 const sal_Int32 nLen
)
141 if( !lcl_getDoubleChar(o_fRetval
, io_rPos
, rStr
, nLen
) )
144 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
149 bool lcl_importNumberAndSpaces(sal_Int32
& o_nRetval
,
151 const ::rtl::OUString
& rStr
,
152 const sal_Int32 nLen
)
154 sal_Unicode
aChar( rStr
[io_rPos
] );
155 ::rtl::OUStringBuffer sNumberString
;
157 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
159 sNumberString
.append(rStr
[io_rPos
]);
160 aChar
= rStr
[++io_rPos
];
163 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
165 sNumberString
.append(rStr
[io_rPos
]);
166 aChar
= rStr
[++io_rPos
];
169 if(sNumberString
.getLength())
171 o_nRetval
= sNumberString
.makeStringAndClear().toInt32();
172 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
180 void lcl_skipNumber(sal_Int32
& io_rPos
,
181 const ::rtl::OUString
& rStr
,
182 const sal_Int32 nLen
)
184 bool bSignAllowed(true);
186 while(io_rPos
< nLen
&& lcl_isOnNumberChar(rStr
, io_rPos
, bSignAllowed
))
188 bSignAllowed
= false;
193 void lcl_skipDouble(sal_Int32
& io_rPos
,
194 const ::rtl::OUString
& rStr
,
195 const sal_Int32
/*nLen*/)
197 sal_Unicode
aChar( rStr
[io_rPos
] );
199 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
200 aChar
= rStr
[++io_rPos
];
202 while((sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
203 || sal_Unicode('.') == aChar
)
205 aChar
= rStr
[++io_rPos
];
208 if(sal_Unicode('e') == aChar
|| sal_Unicode('E') == aChar
)
210 aChar
= rStr
[++io_rPos
];
212 if(sal_Unicode('+') == aChar
|| sal_Unicode('-') == aChar
)
213 aChar
= rStr
[++io_rPos
];
215 while(sal_Unicode('0') <= aChar
&& sal_Unicode('9') >= aChar
)
217 aChar
= rStr
[++io_rPos
];
221 void lcl_skipNumberAndSpacesAndCommas(sal_Int32
& io_rPos
,
222 const ::rtl::OUString
& rStr
,
223 const sal_Int32 nLen
)
225 lcl_skipNumber(io_rPos
, rStr
, nLen
);
226 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
229 // #100617# Allow to skip doubles, too.
230 void lcl_skipDoubleAndSpacesAndCommas(sal_Int32
& io_rPos
,
231 const ::rtl::OUString
& rStr
,
232 const sal_Int32 nLen
)
234 lcl_skipDouble(io_rPos
, rStr
, nLen
);
235 lcl_skipSpacesAndCommas(io_rPos
, rStr
, nLen
);
238 void lcl_putNumberChar( ::rtl::OUStringBuffer
& rStr
,
241 rStr
.append( fValue
);
244 void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer
& rStr
,
247 bool bUseRelativeCoordinates
)
249 if( bUseRelativeCoordinates
)
252 const sal_Int32
aLen( rStr
.getLength() );
255 if( lcl_isOnNumberChar(rStr
.charAt(aLen
- 1), false) &&
258 rStr
.append( sal_Unicode(' ') );
262 lcl_putNumberChar(rStr
, fValue
);
265 inline sal_Unicode
lcl_getCommand( sal_Char cUpperCaseCommand
,
266 sal_Char cLowerCaseCommand
,
267 bool bUseRelativeCoordinates
)
269 return bUseRelativeCoordinates
? cLowerCaseCommand
: cUpperCaseCommand
;
273 bool importFromSvgD(B2DPolyPolygon
& o_rPolyPolygon
, const ::rtl::OUString
& rSvgDStatement
)
275 o_rPolyPolygon
.clear();
276 const sal_Int32
nLen(rSvgDStatement
.getLength());
278 bool bIsClosed(false);
279 double nLastX( 0.0 );
280 double nLastY( 0.0 );
281 B2DPolygon aCurrPoly
;
283 // skip initial whitespace
284 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
288 bool bRelative(false);
290 const sal_Unicode
aCurrChar(rSvgDStatement
[nPos
]);
298 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
300 // remember closed state of current polygon
309 // FALLTHROUGH intended
314 if('m' == aCurrChar
|| 'l' == aCurrChar
)
321 // new polygon start, finish old one
322 if(aCurrPoly
.count())
324 // add current polygon
327 closeWithGeometryChange(aCurrPoly
);
330 o_rPolyPolygon
.append(aCurrPoly
);
332 // reset import values
339 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
341 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
345 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
346 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
359 aCurrPoly
.append(B2DPoint(nX
, nY
));
367 // FALLTHROUGH intended
372 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
374 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
376 double nX
, nY(nLastY
);
378 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
389 aCurrPoly
.append(B2DPoint(nX
, nY
));
397 // FALLTHROUGH intended
402 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
404 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
406 double nX(nLastX
), nY
;
408 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
419 aCurrPoly
.append(B2DPoint(nX
, nY
));
427 // FALLTHROUGH intended
432 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
434 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
439 if(!lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
440 if(!lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
441 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
442 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
452 // ensure existance of start point
453 if(!aCurrPoly
.count())
455 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
458 // get first control point. It's the reflection of the PrevControlPoint
459 // of the last point. If not existent, use current point (see SVG)
460 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
461 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
463 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
465 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
466 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
468 // use mirrored previous control point
469 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
470 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
473 // append curved edge
474 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
486 // FALLTHROUGH intended
491 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
493 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
499 if(!lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
500 if(!lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
501 if(!lcl_importDoubleAndSpaces(nX2
, nPos
, rSvgDStatement
, nLen
)) return false;
502 if(!lcl_importDoubleAndSpaces(nY2
, nPos
, rSvgDStatement
, nLen
)) return false;
503 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
504 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
516 // ensure existance of start point
517 if(!aCurrPoly
.count())
519 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
522 // append curved edge
523 aCurrPoly
.appendBezierSegment(B2DPoint(nX1
, nY1
), B2DPoint(nX2
, nY2
), B2DPoint(nX
, nY
));
532 // #100617# quadratic beziers are imported as cubic ones
536 // FALLTHROUGH intended
541 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
543 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
548 if(!lcl_importDoubleAndSpaces(nX1
, nPos
, rSvgDStatement
, nLen
)) return false;
549 if(!lcl_importDoubleAndSpaces(nY1
, nPos
, rSvgDStatement
, nLen
)) return false;
550 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
551 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
561 // calculate the cubic bezier coefficients from the quadratic ones
562 const double nX1Prime((nX1
* 2.0 + nLastX
) / 3.0);
563 const double nY1Prime((nY1
* 2.0 + nLastY
) / 3.0);
564 const double nX2Prime((nX1
* 2.0 + nX
) / 3.0);
565 const double nY2Prime((nY1
* 2.0 + nY
) / 3.0);
567 // ensure existance of start point
568 if(!aCurrPoly
.count())
570 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
573 // append curved edge
574 aCurrPoly
.appendBezierSegment(B2DPoint(nX1Prime
, nY1Prime
), B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
583 // #100617# relative quadratic beziers are imported as cubic
587 // FALLTHROUGH intended
592 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
594 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
598 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
599 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
607 // ensure existance of start point
608 if(!aCurrPoly
.count())
610 aCurrPoly
.append(B2DPoint(nLastX
, nLastY
));
613 // get first control point. It's the reflection of the PrevControlPoint
614 // of the last point. If not existent, use current point (see SVG)
615 B2DPoint
aPrevControl(B2DPoint(nLastX
, nLastY
));
616 const sal_uInt32
nIndex(aCurrPoly
.count() - 1);
617 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(nIndex
));
619 if(aCurrPoly
.areControlPointsUsed() && aCurrPoly
.isPrevControlPointUsed(nIndex
))
621 const B2DPoint
aPrevControlPoint(aCurrPoly
.getPrevControlPoint(nIndex
));
623 // use mirrored previous control point
624 aPrevControl
.setX((2.0 * aPrevPoint
.getX()) - aPrevControlPoint
.getX());
625 aPrevControl
.setY((2.0 * aPrevPoint
.getY()) - aPrevControlPoint
.getY());
628 if(!aPrevControl
.equal(aPrevPoint
))
630 // there is a prev control point, and we have the already mirrored one
631 // in aPrevControl. We also need the quadratic control point for this
632 // new quadratic segment to calculate the 2nd cubic control point
633 const B2DPoint
aQuadControlPoint(
634 ((3.0 * aPrevControl
.getX()) - aPrevPoint
.getX()) / 2.0,
635 ((3.0 * aPrevControl
.getY()) - aPrevPoint
.getY()) / 2.0);
637 // calculate the cubic bezier coefficients from the quadratic ones.
638 const double nX2Prime((aQuadControlPoint
.getX() * 2.0 + nX
) / 3.0);
639 const double nY2Prime((aQuadControlPoint
.getY() * 2.0 + nY
) / 3.0);
641 // append curved edge, use mirrored cubic control point directly
642 aCurrPoly
.appendBezierSegment(aPrevControl
, B2DPoint(nX2Prime
, nY2Prime
), B2DPoint(nX
, nY
));
646 // when no previous control, SVG says to use current point -> straight line.
647 // Just add end point
648 aCurrPoly
.append(B2DPoint(nX
, nY
));
661 // FALLTHROUGH intended
666 lcl_skipSpaces(nPos
, rSvgDStatement
, nLen
);
668 while(nPos
< nLen
&& lcl_isOnNumberChar(rSvgDStatement
, nPos
))
671 double fRX
, fRY
, fPhi
;
672 sal_Int32 bLargeArcFlag
, bSweepFlag
;
674 if(!lcl_importDoubleAndSpaces(fRX
, nPos
, rSvgDStatement
, nLen
)) return false;
675 if(!lcl_importDoubleAndSpaces(fRY
, nPos
, rSvgDStatement
, nLen
)) return false;
676 if(!lcl_importDoubleAndSpaces(fPhi
, nPos
, rSvgDStatement
, nLen
)) return false;
677 if(!lcl_importNumberAndSpaces(bLargeArcFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
678 if(!lcl_importNumberAndSpaces(bSweepFlag
, nPos
, rSvgDStatement
, nLen
)) return false;
679 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgDStatement
, nLen
)) return false;
680 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgDStatement
, nLen
)) return false;
688 const B2DPoint
aPrevPoint(aCurrPoly
.getB2DPoint(aCurrPoly
.count() - 1));
690 if( nX
== nLastX
&& nY
== nLastY
)
691 continue; // start==end -> skip according to SVG spec
693 if( fRX
== 0.0 || fRY
== 0.0 )
695 // straight line segment according to SVG spec
696 aCurrPoly
.append(B2DPoint(nX
, nY
));
700 // normalize according to SVG spec
701 fRX
=fabs(fRX
); fRY
=fabs(fRY
);
703 // from the SVG spec, appendix F.6.4
705 // |x1'| |cos phi sin phi| |(x1 - x2)/2|
706 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
707 const B2DPoint
p1(nLastX
, nLastY
);
708 const B2DPoint
p2(nX
, nY
);
709 B2DHomMatrix
aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi
*M_PI
/180));
711 const B2DPoint
p1_prime( aTransform
* B2DPoint(((p1
-p2
)/2.0)) );
713 // ______________________________________ rx y1'
714 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
715 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
717 // chose + if f_A != f_S
718 // chose - if f_A = f_S
719 B2DPoint aCenter_prime
;
720 const double fRadicant(
721 (fRX
*fRX
*fRY
*fRY
- fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() - fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX())/
722 (fRX
*fRX
*p1_prime
.getY()*p1_prime
.getY() + fRY
*fRY
*p1_prime
.getX()*p1_prime
.getX()));
723 if( fRadicant
< 0.0 )
725 // no solution - according to SVG
726 // spec, scale up ellipse
727 // uniformly such that it passes
728 // through end points (denominator
729 // of radicant solved for fRY,
731 const double fRatio(fRX
/fRY
);
732 const double fRadicant2(
733 p1_prime
.getY()*p1_prime
.getY() +
734 p1_prime
.getX()*p1_prime
.getX()/(fRatio
*fRatio
));
735 if( fRadicant2
< 0.0 )
737 // only trivial solution, one
738 // of the axes 0 -> straight
739 // line segment according to
741 aCurrPoly
.append(B2DPoint(nX
, nY
));
745 fRY
=sqrt(fRadicant2
);
748 // keep center_prime forced to (0,0)
752 const double fFactor(
753 (bLargeArcFlag
==bSweepFlag
? -1.0 : 1.0) *
756 // actually calculate center_prime
757 aCenter_prime
= B2DPoint(
758 fFactor
*fRX
*p1_prime
.getY()/fRY
,
759 -fFactor
*fRY
*p1_prime
.getX()/fRX
);
763 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
766 // 1 | (x1' - cx')/rx |
767 // theta1 = angle(( ), | | )
768 // 0 | (y1' - cy')/ry |
769 const B2DPoint
aRadii(fRX
,fRY
);
771 B2DVector(1.0,0.0).angle(
772 (p1_prime
-aCenter_prime
)/aRadii
));
774 // |1| | (-x1' - cx')/rx |
775 // theta2 = angle( | | , | | )
776 // |0| | (-y1' - cy')/ry |
778 B2DVector(1.0,0.0).angle(
779 (-p1_prime
-aCenter_prime
)/aRadii
));
781 // map both angles to [0,2pi)
782 fTheta1
= fmod(2*M_PI
+fTheta1
,2*M_PI
);
783 fTheta2
= fmod(2*M_PI
+fTheta2
,2*M_PI
);
785 // make sure the large arc is taken
787 // createPolygonFromEllipseSegment()
788 // normalizes to e.g. cw arc)
789 const bool bFlipSegment( (bLargeArcFlag
!=0) ==
790 (fmod(fTheta2
+2*M_PI
-fTheta1
,
793 std::swap(fTheta1
,fTheta2
);
795 // finally, create bezier polygon from this
797 tools::createPolygonFromUnitEllipseSegment(
800 // transform ellipse by rotation & move to final center
801 aTransform
= basegfx::tools::createScaleB2DHomMatrix(fRX
, fRY
);
802 aTransform
.translate(aCenter_prime
.getX(),
803 aCenter_prime
.getY());
804 aTransform
.rotate(fPhi
*M_PI
/180);
805 const B2DPoint
aOffset((p1
+p2
)/2.0);
806 aTransform
.translate(aOffset
.getX(),
808 aSegment
.transform(aTransform
);
810 // createPolygonFromEllipseSegment()
811 // always creates arcs that are
812 // positively oriented - flip polygon
813 // if we swapped angles above
816 aCurrPoly
.append(aSegment
);
828 OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
829 OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar
);
836 if(aCurrPoly
.count())
838 // end-process last poly
841 closeWithGeometryChange(aCurrPoly
);
844 o_rPolyPolygon
.append(aCurrPoly
);
850 bool importFromSvgPoints( B2DPolygon
& o_rPoly
,
851 const ::rtl::OUString
& rSvgPointsAttribute
)
854 const sal_Int32
nLen(rSvgPointsAttribute
.getLength());
858 // skip initial whitespace
859 lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
863 if(!lcl_importDoubleAndSpaces(nX
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
864 if(!lcl_importDoubleAndSpaces(nY
, nPos
, rSvgPointsAttribute
, nLen
)) return false;
867 o_rPoly
.append(B2DPoint(nX
, nY
));
869 // skip to next number, or finish
870 lcl_skipSpaces(nPos
, rSvgPointsAttribute
, nLen
);
876 ::rtl::OUString
exportToSvgD(
877 const B2DPolyPolygon
& rPolyPolygon
,
878 bool bUseRelativeCoordinates
,
879 bool bDetectQuadraticBeziers
)
881 const sal_uInt32
nCount(rPolyPolygon
.count());
882 ::rtl::OUStringBuffer aResult
;
883 B2DPoint
aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
885 for(sal_uInt32
i(0); i
< nCount
; i
++)
887 const B2DPolygon
aPolygon(rPolyPolygon
.getB2DPolygon(i
));
888 const sal_uInt32
nPointCount(aPolygon
.count());
892 const bool bPolyUsesControlPoints(aPolygon
.areControlPointsUsed());
893 const sal_uInt32
nEdgeCount(aPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
894 sal_Unicode
aLastSVGCommand(' '); // last SVG command char
895 B2DPoint aLeft
, aRight
; // for quadratic bezier test
897 // handle polygon start point
898 B2DPoint
aEdgeStart(aPolygon
.getB2DPoint(0));
899 aResult
.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates
));
900 lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
901 lcl_putNumberCharWithSpace(aResult
, aEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
902 aLastSVGCommand
= lcl_getCommand('L', 'l', bUseRelativeCoordinates
);
903 aCurrentSVGPosition
= aEdgeStart
;
905 for(sal_uInt32
nIndex(0); nIndex
< nEdgeCount
; nIndex
++)
907 // prepare access to next point
908 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
909 const B2DPoint
aEdgeEnd(aPolygon
.getB2DPoint(nNextIndex
));
911 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
912 const bool bEdgeIsBezier(bPolyUsesControlPoints
913 && (aPolygon
.isNextControlPointUsed(nIndex
) || aPolygon
.isPrevControlPointUsed(nNextIndex
)));
917 // handle bezier edge
918 const B2DPoint
aControlEdgeStart(aPolygon
.getNextControlPoint(nIndex
));
919 const B2DPoint
aControlEdgeEnd(aPolygon
.getPrevControlPoint(nNextIndex
));
920 bool bIsQuadraticBezier(false);
922 // check continuity at current edge's start point. For SVG, do NOT use an
923 // existing continuity since no 'S' or 's' statement should be written. At
924 // import, that 'previous' control vector is not available. SVG documentation
925 // says for interpretation:
927 // "(If there is no previous command or if the previous command was
928 // not an C, c, S or s, assume the first control point is coincident
929 // with the current point.)"
931 // That's what is done from our import, so avoid exporting it as first statement
933 const bool bSymmetricAtEdgeStart(
935 && CONTINUITY_C2
== aPolygon
.getContinuityInPoint(nIndex
));
937 if(bDetectQuadraticBeziers
)
939 // check for quadratic beziers - that's
940 // the case if both control points are in
941 // the same place when they are prolonged
942 // to the common quadratic control point
944 // Left: P = (3P1 - P0) / 2
945 // Right: P = (3P2 - P3) / 2
946 aLeft
= B2DPoint((3.0 * aControlEdgeStart
- aEdgeStart
) / 2.0);
947 aRight
= B2DPoint((3.0 * aControlEdgeEnd
- aEdgeEnd
) / 2.0);
948 bIsQuadraticBezier
= aLeft
.equal(aRight
);
951 if(bIsQuadraticBezier
)
953 // approximately equal, export as quadratic bezier
954 if(bSymmetricAtEdgeStart
)
956 const sal_Unicode
aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates
));
958 if(aLastSVGCommand
!= aCommand
)
960 aResult
.append(aCommand
);
961 aLastSVGCommand
= aCommand
;
964 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
965 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
966 aLastSVGCommand
= aCommand
;
967 aCurrentSVGPosition
= aEdgeEnd
;
971 const sal_Unicode
aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates
));
973 if(aLastSVGCommand
!= aCommand
)
975 aResult
.append(aCommand
);
976 aLastSVGCommand
= aCommand
;
979 lcl_putNumberCharWithSpace(aResult
, aLeft
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
980 lcl_putNumberCharWithSpace(aResult
, aLeft
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
981 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
982 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
983 aLastSVGCommand
= aCommand
;
984 aCurrentSVGPosition
= aEdgeEnd
;
989 // export as cubic bezier
990 if(bSymmetricAtEdgeStart
)
992 const sal_Unicode
aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates
));
994 if(aLastSVGCommand
!= aCommand
)
996 aResult
.append(aCommand
);
997 aLastSVGCommand
= aCommand
;
1000 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1001 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1002 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1003 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1004 aLastSVGCommand
= aCommand
;
1005 aCurrentSVGPosition
= aEdgeEnd
;
1009 const sal_Unicode
aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates
));
1011 if(aLastSVGCommand
!= aCommand
)
1013 aResult
.append(aCommand
);
1014 aLastSVGCommand
= aCommand
;
1017 lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1018 lcl_putNumberCharWithSpace(aResult
, aControlEdgeStart
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1019 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1020 lcl_putNumberCharWithSpace(aResult
, aControlEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1021 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1022 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1023 aLastSVGCommand
= aCommand
;
1024 aCurrentSVGPosition
= aEdgeEnd
;
1033 // it's a closed polygon's last edge and it's not a bezier edge, so there is
1034 // no need to write it
1038 const bool bXEqual(aEdgeStart
.getX() == aEdgeEnd
.getX());
1039 const bool bYEqual(aEdgeStart
.getY() == aEdgeEnd
.getY());
1041 if(bXEqual
&& bYEqual
)
1043 // point is a double point; do not export at all
1047 // export as vertical line
1048 const sal_Unicode
aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates
));
1050 if(aLastSVGCommand
!= aCommand
)
1052 aResult
.append(aCommand
);
1053 aLastSVGCommand
= aCommand
;
1056 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1057 aCurrentSVGPosition
= aEdgeEnd
;
1061 // export as horizontal line
1062 const sal_Unicode
aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates
));
1064 if(aLastSVGCommand
!= aCommand
)
1066 aResult
.append(aCommand
);
1067 aLastSVGCommand
= aCommand
;
1070 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1071 aCurrentSVGPosition
= aEdgeEnd
;
1076 const sal_Unicode
aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates
));
1078 if(aLastSVGCommand
!= aCommand
)
1080 aResult
.append(aCommand
);
1081 aLastSVGCommand
= aCommand
;
1084 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getX(), aCurrentSVGPosition
.getX(), bUseRelativeCoordinates
);
1085 lcl_putNumberCharWithSpace(aResult
, aEdgeEnd
.getY(), aCurrentSVGPosition
.getY(), bUseRelativeCoordinates
);
1086 aCurrentSVGPosition
= aEdgeEnd
;
1091 // prepare edge start for next loop step
1092 aEdgeStart
= aEdgeEnd
;
1095 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1096 if(aPolygon
.isClosed())
1098 aResult
.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates
));
1103 return aResult
.makeStringAndClear();