merge the formfield patch from ooo-build
[ooovba.git] / basegfx / source / polygon / b2dsvgpolypolygon.cxx
blob2d993a4183ad1e4d2101d570227dca22e6b03be0
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: b2dsvgpolypolygon.cxx,v $
10 * $Revision: 1.10 $
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>
42 namespace basegfx
44 namespace tools
46 namespace
48 void lcl_skipSpaces(sal_Int32& io_rPos,
49 const ::rtl::OUString& rStr,
50 const sal_Int32 nLen)
52 while( io_rPos < nLen &&
53 sal_Unicode(' ') == rStr[io_rPos] )
55 ++io_rPos;
59 void lcl_skipSpacesAndCommas(sal_Int32& io_rPos,
60 const ::rtl::OUString& rStr,
61 const sal_Int32 nLen)
63 while(io_rPos < nLen
64 && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
66 ++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) );
76 return bPredicate;
79 inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
81 return lcl_isOnNumberChar(rStr[nPos],
82 bSignAllowed);
85 bool lcl_getDoubleChar(double& o_fRetval,
86 sal_Int32& io_rPos,
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(),
128 (sal_Unicode)('.'),
129 (sal_Unicode)(','),
130 &eStatus,
131 NULL );
132 return ( eStatus == rtl_math_ConversionStatus_Ok );
135 return false;
138 bool lcl_importDoubleAndSpaces( double& o_fRetval,
139 sal_Int32& io_rPos,
140 const ::rtl::OUString& rStr,
141 const sal_Int32 nLen )
143 if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) )
144 return false;
146 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
148 return true;
151 bool lcl_importNumberAndSpaces(sal_Int32& o_nRetval,
152 sal_Int32& io_rPos,
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);
176 return true;
179 return false;
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;
191 ++io_rPos;
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,
241 double fValue )
243 rStr.append( fValue );
246 void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer& rStr,
247 double fValue,
248 double fOldValue,
249 bool bUseRelativeCoordinates )
251 if( bUseRelativeCoordinates )
252 fValue -= fOldValue;
254 const sal_Int32 aLen( rStr.getLength() );
255 if(aLen)
257 if( lcl_isOnNumberChar(rStr.charAt(aLen - 1), false) &&
258 fValue >= 0.0 )
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());
279 sal_Int32 nPos(0);
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);
288 while(nPos < nLen)
290 bool bRelative(false);
291 bool bMoveTo(false);
292 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
294 switch(aCurrChar)
296 case 'z' :
297 case 'Z' :
299 nPos++;
300 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
302 // remember closed state of current polygon
303 bIsClosed = true;
304 break;
307 case 'm' :
308 case 'M' :
310 bMoveTo = true;
311 // FALLTHROUGH intended
313 case 'l' :
314 case 'L' :
316 if('m' == aCurrChar || 'l' == aCurrChar)
318 bRelative = true;
321 if(bMoveTo)
323 // new polygon start, finish old one
324 if(aCurrPoly.count())
326 // add current polygon
327 if(bIsClosed)
329 closeWithGeometryChange(aCurrPoly);
332 o_rPolyPolygon.append(aCurrPoly);
334 // reset import values
335 bIsClosed = false;
336 aCurrPoly.clear();
340 nPos++;
341 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
343 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
345 double nX, nY;
347 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
348 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
350 if(bRelative)
352 nX += nLastX;
353 nY += nLastY;
356 // set last position
357 nLastX = nX;
358 nLastY = nY;
360 // add point
361 aCurrPoly.append(B2DPoint(nX, nY));
363 break;
366 case 'h' :
368 bRelative = true;
369 // FALLTHROUGH intended
371 case 'H' :
373 nPos++;
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;
382 if(bRelative)
384 nX += nLastX;
387 // set last position
388 nLastX = nX;
390 // add point
391 aCurrPoly.append(B2DPoint(nX, nY));
393 break;
396 case 'v' :
398 bRelative = true;
399 // FALLTHROUGH intended
401 case 'V' :
403 nPos++;
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;
412 if(bRelative)
414 nY += nLastY;
417 // set last position
418 nLastY = nY;
420 // add point
421 aCurrPoly.append(B2DPoint(nX, nY));
423 break;
426 case 's' :
428 bRelative = true;
429 // FALLTHROUGH intended
431 case 'S' :
433 nPos++;
434 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
436 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
438 double nX, nY;
439 double nX2, nY2;
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;
446 if(bRelative)
448 nX2 += nLastX;
449 nY2 += nLastY;
450 nX += nLastX;
451 nY += nLastY;
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));
478 // set last position
479 nLastX = nX;
480 nLastY = nY;
482 break;
485 case 'c' :
487 bRelative = true;
488 // FALLTHROUGH intended
490 case 'C' :
492 nPos++;
493 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
495 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
497 double nX, nY;
498 double nX1, nY1;
499 double nX2, nY2;
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;
508 if(bRelative)
510 nX1 += nLastX;
511 nY1 += nLastY;
512 nX2 += nLastX;
513 nY2 += nLastY;
514 nX += nLastX;
515 nY += nLastY;
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));
527 // set last position
528 nLastX = nX;
529 nLastY = nY;
531 break;
534 // #100617# quadratic beziers are imported as cubic ones
535 case 'q' :
537 bRelative = true;
538 // FALLTHROUGH intended
540 case 'Q' :
542 nPos++;
543 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
545 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
547 double nX, nY;
548 double nX1, nY1;
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;
555 if(bRelative)
557 nX1 += nLastX;
558 nY1 += nLastY;
559 nX += nLastX;
560 nY += nLastY;
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));
578 // set last position
579 nLastX = nX;
580 nLastY = nY;
582 break;
585 // #100617# relative quadratic beziers are imported as cubic
586 case 't' :
588 bRelative = true;
589 // FALLTHROUGH intended
591 case 'T' :
593 nPos++;
594 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
596 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
598 double nX, nY;
600 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
601 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
603 if(bRelative)
605 nX += nLastX;
606 nY += nLastY;
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));
646 else
648 // when no previous control, SVG says to use current point -> straight line.
649 // Just add end point
650 aCurrPoly.append(B2DPoint(nX, nY));
653 // set last position
654 nLastX = nX;
655 nLastY = nY;
657 break;
660 case 'a' :
662 bRelative = true;
663 // FALLTHROUGH intended
665 case 'A' :
667 nPos++;
668 lcl_skipSpaces(nPos, rSvgDStatement, nLen);
670 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
672 double nX, nY;
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;
684 if(bRelative)
686 nX += nLastX;
687 nY += nLastY;
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));
700 else
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'
718 // rx
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,
732 // with s=fRX/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
742 // SVG spec
743 aCurrPoly.append(B2DPoint(nX, nY));
744 continue;
747 fRY=sqrt(fRadicant2);
748 fRX=fRatio*fRY;
750 // keep center_prime forced to (0,0)
752 else
754 const double fFactor(
755 (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
756 sqrt(fRadicant));
758 // actually calculate center_prime
759 aCenter_prime = B2DPoint(
760 fFactor*fRX*p1_prime.getY()/fRY,
761 -fFactor*fRY*p1_prime.getX()/fRX);
764 // + u - v
765 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
766 // - ||u|| ||v||
768 // 1 | (x1' - cx')/rx |
769 // theta1 = angle(( ), | | )
770 // 0 | (y1' - cy')/ry |
771 const B2DPoint aRadii(fRX,fRY);
772 double fTheta1(
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 |
779 double fTheta2(
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
788 // (since
789 // createPolygonFromEllipseSegment()
790 // normalizes to e.g. cw arc)
791 const bool bFlipSegment( (bLargeArcFlag!=0) ==
792 (fmod(fTheta2+2*M_PI-fTheta1,
793 2*M_PI)<M_PI) );
794 if( bFlipSegment )
795 std::swap(fTheta1,fTheta2);
797 // finally, create bezier polygon from this
798 B2DPolygon aSegment(
799 tools::createPolygonFromUnitEllipseSegment(
800 fTheta1, fTheta2 ));
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(),
810 aOffset.getY());
811 aSegment.transform(aTransform);
813 // createPolygonFromEllipseSegment()
814 // always creates arcs that are
815 // positively oriented - flip polygon
816 // if we swapped angles above
817 if( bFlipSegment )
818 aSegment.flip();
819 aCurrPoly.append(aSegment);
822 // set last position
823 nLastX = nX;
824 nLastY = nY;
826 break;
829 default:
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);
833 ++nPos;
834 break;
839 if(aCurrPoly.count())
841 // end-process last poly
842 if(bIsClosed)
844 closeWithGeometryChange(aCurrPoly);
847 o_rPolyPolygon.append(aCurrPoly);
850 return true;
853 bool importFromSvgPoints( B2DPolygon& o_rPoly,
854 const ::rtl::OUString& rSvgPointsAttribute )
856 o_rPoly.clear();
857 const sal_Int32 nLen(rSvgPointsAttribute.getLength());
858 sal_Int32 nPos(0);
859 double nX, nY;
861 // skip initial whitespace
862 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
864 while(nPos < nLen)
866 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
867 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
869 // add point
870 o_rPoly.append(B2DPoint(nX, nY));
872 // skip to next number, or finish
873 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
876 return true;
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());
893 if(nPointCount)
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)));
918 if(bEdgeIsBezier)
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
935 // is necessary.
936 const bool bSymmetricAtEdgeStart(
937 0 != nIndex
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;
972 else
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;
990 else
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;
1010 else
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;
1031 else
1033 // straight edge
1034 if(0 == nNextIndex)
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
1039 else
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
1048 else if(bXEqual)
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;
1062 else if(bYEqual)
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;
1076 else
1078 // export as line
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();
1111 // eof