merged tag ooo/OOO330_m14
[LibreOffice.git] / basegfx / source / polygon / b2dsvgpolypolygon.cxx
blobd2815337edaf362dcea6e8949b87328612473fd0
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>
40 namespace basegfx
42 namespace tools
44 namespace
46 void lcl_skipSpaces(sal_Int32& io_rPos,
47 const ::rtl::OUString& rStr,
48 const sal_Int32 nLen)
50 while( io_rPos < nLen &&
51 sal_Unicode(' ') == rStr[io_rPos] )
53 ++io_rPos;
57 void lcl_skipSpacesAndCommas(sal_Int32& io_rPos,
58 const ::rtl::OUString& rStr,
59 const sal_Int32 nLen)
61 while(io_rPos < nLen
62 && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
64 ++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) );
74 return bPredicate;
77 inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
79 return lcl_isOnNumberChar(rStr[nPos],
80 bSignAllowed);
83 bool lcl_getDoubleChar(double& o_fRetval,
84 sal_Int32& io_rPos,
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(),
126 (sal_Unicode)('.'),
127 (sal_Unicode)(','),
128 &eStatus,
129 NULL );
130 return ( eStatus == rtl_math_ConversionStatus_Ok );
133 return false;
136 bool lcl_importDoubleAndSpaces( double& o_fRetval,
137 sal_Int32& io_rPos,
138 const ::rtl::OUString& rStr,
139 const sal_Int32 nLen )
141 if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) )
142 return false;
144 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
146 return true;
149 bool lcl_importNumberAndSpaces(sal_Int32& o_nRetval,
150 sal_Int32& io_rPos,
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);
174 return true;
177 return false;
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;
189 ++io_rPos;
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,
239 double fValue )
241 rStr.append( fValue );
244 void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer& rStr,
245 double fValue,
246 double fOldValue,
247 bool bUseRelativeCoordinates )
249 if( bUseRelativeCoordinates )
250 fValue -= fOldValue;
252 const sal_Int32 aLen( rStr.getLength() );
253 if(aLen)
255 if( lcl_isOnNumberChar(rStr.charAt(aLen - 1), false) &&
256 fValue >= 0.0 )
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());
277 sal_Int32 nPos(0);
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);
286 while(nPos < nLen)
288 bool bRelative(false);
289 bool bMoveTo(false);
290 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
292 switch(aCurrChar)
294 case 'z' :
295 case 'Z' :
297 nPos++;
298 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
300 // remember closed state of current polygon
301 bIsClosed = true;
302 break;
305 case 'm' :
306 case 'M' :
308 bMoveTo = true;
309 // FALLTHROUGH intended
311 case 'l' :
312 case 'L' :
314 if('m' == aCurrChar || 'l' == aCurrChar)
316 bRelative = true;
319 if(bMoveTo)
321 // new polygon start, finish old one
322 if(aCurrPoly.count())
324 // add current polygon
325 if(bIsClosed)
327 closeWithGeometryChange(aCurrPoly);
330 o_rPolyPolygon.append(aCurrPoly);
332 // reset import values
333 bIsClosed = false;
334 aCurrPoly.clear();
338 nPos++;
339 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
341 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
343 double nX, nY;
345 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
346 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
348 if(bRelative)
350 nX += nLastX;
351 nY += nLastY;
354 // set last position
355 nLastX = nX;
356 nLastY = nY;
358 // add point
359 aCurrPoly.append(B2DPoint(nX, nY));
361 break;
364 case 'h' :
366 bRelative = true;
367 // FALLTHROUGH intended
369 case 'H' :
371 nPos++;
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;
380 if(bRelative)
382 nX += nLastX;
385 // set last position
386 nLastX = nX;
388 // add point
389 aCurrPoly.append(B2DPoint(nX, nY));
391 break;
394 case 'v' :
396 bRelative = true;
397 // FALLTHROUGH intended
399 case 'V' :
401 nPos++;
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;
410 if(bRelative)
412 nY += nLastY;
415 // set last position
416 nLastY = nY;
418 // add point
419 aCurrPoly.append(B2DPoint(nX, nY));
421 break;
424 case 's' :
426 bRelative = true;
427 // FALLTHROUGH intended
429 case 'S' :
431 nPos++;
432 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
434 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
436 double nX, nY;
437 double nX2, nY2;
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;
444 if(bRelative)
446 nX2 += nLastX;
447 nY2 += nLastY;
448 nX += nLastX;
449 nY += nLastY;
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));
476 // set last position
477 nLastX = nX;
478 nLastY = nY;
480 break;
483 case 'c' :
485 bRelative = true;
486 // FALLTHROUGH intended
488 case 'C' :
490 nPos++;
491 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
493 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
495 double nX, nY;
496 double nX1, nY1;
497 double nX2, nY2;
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;
506 if(bRelative)
508 nX1 += nLastX;
509 nY1 += nLastY;
510 nX2 += nLastX;
511 nY2 += nLastY;
512 nX += nLastX;
513 nY += nLastY;
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));
525 // set last position
526 nLastX = nX;
527 nLastY = nY;
529 break;
532 // #100617# quadratic beziers are imported as cubic ones
533 case 'q' :
535 bRelative = true;
536 // FALLTHROUGH intended
538 case 'Q' :
540 nPos++;
541 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
543 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
545 double nX, nY;
546 double nX1, nY1;
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;
553 if(bRelative)
555 nX1 += nLastX;
556 nY1 += nLastY;
557 nX += nLastX;
558 nY += nLastY;
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));
576 // set last position
577 nLastX = nX;
578 nLastY = nY;
580 break;
583 // #100617# relative quadratic beziers are imported as cubic
584 case 't' :
586 bRelative = true;
587 // FALLTHROUGH intended
589 case 'T' :
591 nPos++;
592 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
594 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
596 double nX, nY;
598 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
599 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
601 if(bRelative)
603 nX += nLastX;
604 nY += nLastY;
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));
644 else
646 // when no previous control, SVG says to use current point -> straight line.
647 // Just add end point
648 aCurrPoly.append(B2DPoint(nX, nY));
651 // set last position
652 nLastX = nX;
653 nLastY = nY;
655 break;
658 case 'a' :
660 bRelative = true;
661 // FALLTHROUGH intended
663 case 'A' :
665 nPos++;
666 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
668 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
670 double nX, nY;
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;
682 if(bRelative)
684 nX += nLastX;
685 nY += nLastY;
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));
698 else
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'
716 // rx
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,
730 // with s=fRX/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
740 // SVG spec
741 aCurrPoly.append(B2DPoint(nX, nY));
742 continue;
745 fRY=sqrt(fRadicant2);
746 fRX=fRatio*fRY;
748 // keep center_prime forced to (0,0)
750 else
752 const double fFactor(
753 (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
754 sqrt(fRadicant));
756 // actually calculate center_prime
757 aCenter_prime = B2DPoint(
758 fFactor*fRX*p1_prime.getY()/fRY,
759 -fFactor*fRY*p1_prime.getX()/fRX);
762 // + u - v
763 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
764 // - ||u|| ||v||
766 // 1 | (x1' - cx')/rx |
767 // theta1 = angle(( ), | | )
768 // 0 | (y1' - cy')/ry |
769 const B2DPoint aRadii(fRX,fRY);
770 double fTheta1(
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 |
777 double fTheta2(
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
786 // (since
787 // createPolygonFromEllipseSegment()
788 // normalizes to e.g. cw arc)
789 const bool bFlipSegment( (bLargeArcFlag!=0) ==
790 (fmod(fTheta2+2*M_PI-fTheta1,
791 2*M_PI)<M_PI) );
792 if( bFlipSegment )
793 std::swap(fTheta1,fTheta2);
795 // finally, create bezier polygon from this
796 B2DPolygon aSegment(
797 tools::createPolygonFromUnitEllipseSegment(
798 fTheta1, fTheta2 ));
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(),
807 aOffset.getY());
808 aSegment.transform(aTransform);
810 // createPolygonFromEllipseSegment()
811 // always creates arcs that are
812 // positively oriented - flip polygon
813 // if we swapped angles above
814 if( bFlipSegment )
815 aSegment.flip();
816 aCurrPoly.append(aSegment);
819 // set last position
820 nLastX = nX;
821 nLastY = nY;
823 break;
826 default:
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);
830 ++nPos;
831 break;
836 if(aCurrPoly.count())
838 // end-process last poly
839 if(bIsClosed)
841 closeWithGeometryChange(aCurrPoly);
844 o_rPolyPolygon.append(aCurrPoly);
847 return true;
850 bool importFromSvgPoints( B2DPolygon& o_rPoly,
851 const ::rtl::OUString& rSvgPointsAttribute )
853 o_rPoly.clear();
854 const sal_Int32 nLen(rSvgPointsAttribute.getLength());
855 sal_Int32 nPos(0);
856 double nX, nY;
858 // skip initial whitespace
859 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
861 while(nPos < nLen)
863 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
864 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
866 // add point
867 o_rPoly.append(B2DPoint(nX, nY));
869 // skip to next number, or finish
870 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
873 return true;
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());
890 if(nPointCount)
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)));
915 if(bEdgeIsBezier)
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
932 // is necessary.
933 const bool bSymmetricAtEdgeStart(
934 0 != nIndex
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;
969 else
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;
987 else
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;
1007 else
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;
1028 else
1030 // straight edge
1031 if(0 == nNextIndex)
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
1036 else
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
1045 else if(bXEqual)
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;
1059 else if(bYEqual)
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;
1073 else
1075 // export as line
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();
1108 // eof