1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
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.
14  *
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).
20  *
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.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_basegfx.hxx"
30 
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>
39 
40 namespace basegfx
41 {
42 	namespace tools
43 	{
44         namespace
45         {
46             void lcl_skipSpaces(sal_Int32& 				io_rPos,
47                                 const ::rtl::OUString& 	rStr,
48                                 const sal_Int32 		nLen)
49             {
50                 while( io_rPos < nLen &&
51                        sal_Unicode(' ') == rStr[io_rPos] )
52                 {
53                     ++io_rPos;
54                 }
55             }
56 
57             void lcl_skipSpacesAndCommas(sal_Int32& 			io_rPos,
58                                          const ::rtl::OUString& rStr,
59                                          const sal_Int32 		nLen)
60             {
61                 while(io_rPos < nLen
62                       && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
63                 {
64                     ++io_rPos;
65                 }
66             }
67 
68             inline bool lcl_isOnNumberChar(const sal_Unicode aChar, bool bSignAllowed = true)
69             {
70                 const bool bPredicate( (sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
71                                        || (bSignAllowed && sal_Unicode('+') == aChar)
72                                        || (bSignAllowed && sal_Unicode('-') == aChar) );
73 
74                 return bPredicate;
75             }
76 
77             inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
78             {
79                 return lcl_isOnNumberChar(rStr[nPos],
80                                           bSignAllowed);
81             }
82 
83             bool lcl_getDoubleChar(double& 					o_fRetval,
84                                    sal_Int32& 				io_rPos,
85                                    const ::rtl::OUString& 	rStr,
86                                    const sal_Int32 			/*nLen*/)
87             {
88                 sal_Unicode aChar( rStr[io_rPos] );
89                 ::rtl::OUStringBuffer sNumberString;
90 
91                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
92                 {
93                     sNumberString.append(rStr[io_rPos]);
94                     aChar = rStr[++io_rPos];
95                 }
96 
97                 while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
98                       || sal_Unicode('.') == aChar)
99                 {
100                     sNumberString.append(rStr[io_rPos]);
101                     aChar = rStr[++io_rPos];
102                 }
103 
104                 if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
105                 {
106                     sNumberString.append(rStr[io_rPos]);
107                     aChar = rStr[++io_rPos];
108 
109                     if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
110                     {
111                         sNumberString.append(rStr[io_rPos]);
112                         aChar = rStr[++io_rPos];
113                     }
114 
115                     while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
116                     {
117                         sNumberString.append(rStr[io_rPos]);
118                         aChar = rStr[++io_rPos];
119                     }
120                 }
121 
122                 if(sNumberString.getLength())
123                 {
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 );
131                 }
132 
133                 return false;
134             }
135 
136             bool lcl_importDoubleAndSpaces( double& 				o_fRetval,
137                                             sal_Int32& 				io_rPos,
138                                             const ::rtl::OUString& 	rStr,
139                                             const sal_Int32 		nLen )
140             {
141                 if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) )
142                     return false;
143 
144                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
145 
146                 return true;
147             }
148 
149             bool lcl_importNumberAndSpaces(sal_Int32&                o_nRetval,
150                                            sal_Int32& 				io_rPos,
151                                            const ::rtl::OUString& 	rStr,
152                                            const sal_Int32 		nLen)
153             {
154                 sal_Unicode aChar( rStr[io_rPos] );
155                 ::rtl::OUStringBuffer sNumberString;
156 
157                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
158                 {
159                     sNumberString.append(rStr[io_rPos]);
160                     aChar = rStr[++io_rPos];
161                 }
162 
163                 while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
164                 {
165                     sNumberString.append(rStr[io_rPos]);
166                     aChar = rStr[++io_rPos];
167                 }
168 
169                 if(sNumberString.getLength())
170                 {
171                     o_nRetval = sNumberString.makeStringAndClear().toInt32();
172                     lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
173 
174                     return true;
175                 }
176 
177                 return false;
178             }
179 
180             void lcl_skipNumber(sal_Int32& 				io_rPos,
181                                 const ::rtl::OUString& 	rStr,
182                                 const sal_Int32 		nLen)
183             {
184                 bool bSignAllowed(true);
185 
186                 while(io_rPos < nLen && lcl_isOnNumberChar(rStr, io_rPos, bSignAllowed))
187                 {
188                     bSignAllowed = false;
189                     ++io_rPos;
190                 }
191             }
192 
193             void lcl_skipDouble(sal_Int32& 				io_rPos,
194                                 const ::rtl::OUString& 	rStr,
195                                 const sal_Int32 		/*nLen*/)
196             {
197                 sal_Unicode aChar( rStr[io_rPos] );
198 
199                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
200                     aChar = rStr[++io_rPos];
201 
202                 while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
203                       || sal_Unicode('.') == aChar)
204                 {
205                     aChar = rStr[++io_rPos];
206                 }
207 
208                 if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
209                 {
210                     aChar = rStr[++io_rPos];
211 
212                     if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
213                         aChar = rStr[++io_rPos];
214 
215                     while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
216                     {
217                         aChar = rStr[++io_rPos];
218                     }
219                 }
220             }
221             void lcl_skipNumberAndSpacesAndCommas(sal_Int32& 				io_rPos,
222                                                   const ::rtl::OUString& 	rStr,
223                                                   const sal_Int32 			nLen)
224             {
225                 lcl_skipNumber(io_rPos, rStr, nLen);
226                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
227             }
228 
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)
233             {
234                 lcl_skipDouble(io_rPos, rStr, nLen);
235                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
236             }
237 
238             void lcl_putNumberChar( ::rtl::OUStringBuffer& rStr,
239                                     double 		 	       fValue )
240             {
241                 rStr.append( fValue );
242             }
243 
244             void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer& rStr,
245                                              double 		        fValue,
246                                              double 		        fOldValue,
247                                              bool 			        bUseRelativeCoordinates )
248             {
249                 if( bUseRelativeCoordinates )
250                     fValue -= fOldValue;
251 
252                 const sal_Int32 aLen( rStr.getLength() );
253                 if(aLen)
254                 {
255                     if( lcl_isOnNumberChar(rStr.charAt(aLen - 1), false) &&
256                         fValue >= 0.0 )
257                     {
258                         rStr.append( sal_Unicode(' ') );
259                     }
260                 }
261 
262                 lcl_putNumberChar(rStr, fValue);
263             }
264 
265             inline sal_Unicode lcl_getCommand( sal_Char cUpperCaseCommand,
266                                                sal_Char cLowerCaseCommand,
267                                                bool 	bUseRelativeCoordinates )
268             {
269                 return bUseRelativeCoordinates ? cLowerCaseCommand : cUpperCaseCommand;
270             }
271         }
272 
273         bool importFromSvgD(B2DPolyPolygon& o_rPolyPolygon, const ::rtl::OUString& 	rSvgDStatement)
274         {
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;
282 
283 			// skip initial whitespace
284             lcl_skipSpaces(nPos, rSvgDStatement, nLen);
285 
286             while(nPos < nLen)
287             {
288                 bool bRelative(false);
289                 bool bMoveTo(false);
290                 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
291 
292                 switch(aCurrChar)
293                 {
294                     case 'z' :
295                     case 'Z' :
296                     {
297                         nPos++;
298                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
299 
300                         // remember closed state of current polygon
301                         bIsClosed = true;
302                         break;
303                     }
304 
305                     case 'm' :
306                     case 'M' :
307                     {
308                         bMoveTo = true;
309                         // FALLTHROUGH intended
310                     }
311                     case 'l' :
312                     case 'L' :
313                     {
314                         if('m' == aCurrChar || 'l' == aCurrChar)
315 						{
316                             bRelative = true;
317 						}
318 
319                         if(bMoveTo)
320                         {
321 							// new polygon start, finish old one
322                             if(aCurrPoly.count())
323                             {
324 								// add current polygon
325 								if(bIsClosed)
326 								{
327 									closeWithGeometryChange(aCurrPoly);
328 								}
329 
330 								o_rPolyPolygon.append(aCurrPoly);
331 
332 								// reset import values
333 								bIsClosed = false;
334                                 aCurrPoly.clear();
335                             }
336                         }
337 
338                         nPos++;
339                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
340 
341                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
342                         {
343                             double nX, nY;
344 
345                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
346                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
347 
348                             if(bRelative)
349                             {
350                                 nX += nLastX;
351                                 nY += nLastY;
352                             }
353 
354                             // set last position
355                             nLastX = nX;
356                             nLastY = nY;
357 
358                             // add point
359                             aCurrPoly.append(B2DPoint(nX, nY));
360                         }
361                         break;
362                     }
363 
364                     case 'h' :
365                     {
366                         bRelative = true;
367                         // FALLTHROUGH intended
368                     }
369                     case 'H' :
370                     {
371                         nPos++;
372                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
373 
374                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
375                         {
376                             double nX, nY(nLastY);
377 
378                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
379 
380                             if(bRelative)
381 							{
382                                 nX += nLastX;
383 							}
384 
385                             // set last position
386                             nLastX = nX;
387 
388                             // add point
389                             aCurrPoly.append(B2DPoint(nX, nY));
390                         }
391                         break;
392                     }
393 
394                     case 'v' :
395                     {
396                         bRelative = true;
397                         // FALLTHROUGH intended
398                     }
399                     case 'V' :
400                     {
401                         nPos++;
402                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
403 
404                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
405                         {
406                             double nX(nLastX), nY;
407 
408                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
409 
410                             if(bRelative)
411 							{
412                                 nY += nLastY;
413 							}
414 
415                             // set last position
416                             nLastY = nY;
417 
418                             // add point
419                             aCurrPoly.append(B2DPoint(nX, nY));
420                         }
421                         break;
422                     }
423 
424                     case 's' :
425                     {
426                         bRelative = true;
427                         // FALLTHROUGH intended
428                     }
429                     case 'S' :
430                     {
431                         nPos++;
432                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
433 
434                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
435                         {
436                             double nX, nY;
437                             double nX2, nY2;
438 
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;
443 
444                             if(bRelative)
445                             {
446                                 nX2 += nLastX;
447                                 nY2 += nLastY;
448                                 nX += nLastX;
449                                 nY += nLastY;
450                             }
451 
452 							// ensure existance of start point
453 							if(!aCurrPoly.count())
454 							{
455                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
456 							}
457 
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);
462 
463 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
464 							{
465 								const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
466 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
467 
468 								// use mirrored previous control point
469 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
470 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
471 							}
472 
473 							// append curved edge
474 							aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
475 
476                             // set last position
477                             nLastX = nX;
478                             nLastY = nY;
479                         }
480                         break;
481                     }
482 
483                     case 'c' :
484                     {
485                         bRelative = true;
486                         // FALLTHROUGH intended
487                     }
488                     case 'C' :
489                     {
490                         nPos++;
491                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
492 
493                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
494                         {
495                             double nX, nY;
496                             double nX1, nY1;
497                             double nX2, nY2;
498 
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;
505 
506                             if(bRelative)
507                             {
508                                 nX1 += nLastX;
509                                 nY1 += nLastY;
510                                 nX2 += nLastX;
511                                 nY2 += nLastY;
512                                 nX += nLastX;
513                                 nY += nLastY;
514                             }
515 
516 							// ensure existance of start point
517 							if(!aCurrPoly.count())
518 							{
519                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
520 							}
521 
522 							// append curved edge
523 							aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
524 
525                             // set last position
526                             nLastX = nX;
527                             nLastY = nY;
528                         }
529                         break;
530                     }
531 
532                     // #100617# quadratic beziers are imported as cubic ones
533                     case 'q' :
534                     {
535                         bRelative = true;
536                         // FALLTHROUGH intended
537                     }
538                     case 'Q' :
539                     {
540                         nPos++;
541                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
542 
543                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
544                         {
545                             double nX, nY;
546                             double nX1, nY1;
547 
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;
552 
553                             if(bRelative)
554                             {
555                                 nX1 += nLastX;
556                                 nY1 += nLastY;
557                                 nX += nLastX;
558                                 nY += nLastY;
559                             }
560 
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);
566 
567 							// ensure existance of start point
568 							if(!aCurrPoly.count())
569 							{
570                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
571 							}
572 
573 							// append curved edge
574 							aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
575 
576                             // set last position
577                             nLastX = nX;
578                             nLastY = nY;
579                         }
580                         break;
581                     }
582 
583                     // #100617# relative quadratic beziers are imported as cubic
584                     case 't' :
585                     {
586                         bRelative = true;
587                         // FALLTHROUGH intended
588                     }
589                     case 'T' :
590                     {
591                         nPos++;
592                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
593 
594                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
595                         {
596                             double nX, nY;
597 
598                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
599                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
600 
601                             if(bRelative)
602                             {
603                                 nX += nLastX;
604                                 nY += nLastY;
605                             }
606 
607 							// ensure existance of start point
608 							if(!aCurrPoly.count())
609 							{
610                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
611 							}
612 
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));
618 
619 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
620 							{
621 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
622 
623 								// use mirrored previous control point
624 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
625 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
626 							}
627 
628 							if(!aPrevControl.equal(aPrevPoint))
629 							{
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);
636 
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);
640 
641 								// append curved edge, use mirrored cubic control point directly
642 								aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
643 							}
644 							else
645 							{
646 								// when no previous control, SVG says to use current point -> straight line.
647 								// Just add end point
648 								aCurrPoly.append(B2DPoint(nX, nY));
649 							}
650 
651                             // set last position
652                             nLastX = nX;
653                             nLastY = nY;
654                         }
655                         break;
656                     }
657 
658                     case 'a' :
659                     {
660                         bRelative = true;
661                         // FALLTHROUGH intended
662                     }
663                     case 'A' :
664                     {
665                         nPos++;
666                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
667 
668                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
669                         {
670                             double nX, nY;
671                             double fRX, fRY, fPhi;
672                             sal_Int32 bLargeArcFlag, bSweepFlag;
673 
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;
681 
682                             if(bRelative)
683                             {
684                                 nX += nLastX;
685                                 nY += nLastY;
686                             }
687 
688 							const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1));
689 
690                             if( nX == nLastX && nY == nLastY )
691                                 continue; // start==end -> skip according to SVG spec
692 
693                             if( fRX == 0.0 || fRY == 0.0 )
694                             {
695                                 // straight line segment according to SVG spec
696                                 aCurrPoly.append(B2DPoint(nX, nY));
697                             }
698                             else
699                             {
700                                 // normalize according to SVG spec
701                                 fRX=fabs(fRX); fRY=fabs(fRY);
702 
703                                 // from the SVG spec, appendix F.6.4
704 
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));
710 
711                                 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
712 
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 )
724                                 {
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 )
736                                     {
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;
743                                     }
744 
745                                     fRY=sqrt(fRadicant2);
746                                     fRX=fRatio*fRY;
747 
748                                     // keep center_prime forced to (0,0)
749                                 }
750                                 else
751                                 {
752                                     const double fFactor(
753                                         (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
754                                         sqrt(fRadicant));
755 
756                                     // actually calculate center_prime
757                                     aCenter_prime = B2DPoint(
758                                         fFactor*fRX*p1_prime.getY()/fRY,
759                                         -fFactor*fRY*p1_prime.getX()/fRX);
760                                 }
761 
762                                 //              +           u - v
763                                 // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
764                                 //              -        ||u|| ||v||
765 
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));
773 
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));
780 
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);
784 
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);
794 
795                                 // finally, create bezier polygon from this
796                                 B2DPolygon aSegment(
797                                     tools::createPolygonFromUnitEllipseSegment(
798                                         fTheta1, fTheta2 ));
799 
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);
809 
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);
817                             }
818 
819                             // set last position
820                             nLastX = nX;
821                             nLastY = nY;
822                         }
823                         break;
824                     }
825 
826                     default:
827                     {
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;
832                     }
833                 }
834             }
835 
836             if(aCurrPoly.count())
837             {
838                 // end-process last poly
839 				if(bIsClosed)
840 				{
841 					closeWithGeometryChange(aCurrPoly);
842 				}
843 
844 				o_rPolyPolygon.append(aCurrPoly);
845             }
846 
847             return true;
848         }
849 
850         bool importFromSvgPoints( B2DPolygon&            o_rPoly,
851                                   const ::rtl::OUString& rSvgPointsAttribute )
852         {
853             o_rPoly.clear();
854             const sal_Int32 nLen(rSvgPointsAttribute.getLength());
855             sal_Int32 nPos(0);
856             double nX, nY;
857 
858             // skip initial whitespace
859             lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
860 
861             while(nPos < nLen)
862             {
863                 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
864                 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
865 
866                 // add point
867                 o_rPoly.append(B2DPoint(nX, nY));
868 
869                 // skip to next number, or finish
870                 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
871             }
872 
873             return true;
874         }
875 
876         ::rtl::OUString exportToSvgD(
877 			const B2DPolyPolygon& rPolyPolygon,
878 			bool bUseRelativeCoordinates,
879 			bool bDetectQuadraticBeziers)
880         {
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
884 
885             for(sal_uInt32 i(0); i < nCount; i++)
886             {
887                 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
888                 const sal_uInt32 nPointCount(aPolygon.count());
889 
890 				if(nPointCount)
891 				{
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
896 
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;
904 
905 					for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
906 					{
907 						// prepare access to next point
908 						const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
909 						const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
910 
911 						// handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
912 						const bool bEdgeIsBezier(bPolyUsesControlPoints
913 							&& (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
914 
915 						if(bEdgeIsBezier)
916 						{
917 							// handle bezier edge
918 							const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
919 							const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
920 							bool bIsQuadraticBezier(false);
921 
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:
926 							//
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.)"
930 							//
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));
936 
937 							if(bDetectQuadraticBeziers)
938 							{
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
943 								//
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);
949 							}
950 
951 							if(bIsQuadraticBezier)
952 							{
953 								// approximately equal, export as quadratic bezier
954 								if(bSymmetricAtEdgeStart)
955 								{
956 									const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates));
957 
958 									if(aLastSVGCommand != aCommand)
959 									{
960                                         aResult.append(aCommand);
961 										aLastSVGCommand = aCommand;
962 									}
963 
964 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
965 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
966 									aLastSVGCommand = aCommand;
967 									aCurrentSVGPosition = aEdgeEnd;
968 								}
969 								else
970 								{
971 									const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
972 
973 									if(aLastSVGCommand != aCommand)
974 									{
975                                         aResult.append(aCommand);
976 										aLastSVGCommand = aCommand;
977 									}
978 
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;
985 								}
986 							}
987 							else
988 							{
989 								// export as cubic bezier
990 								if(bSymmetricAtEdgeStart)
991 								{
992 									const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
993 
994 									if(aLastSVGCommand != aCommand)
995 									{
996                                         aResult.append(aCommand);
997 										aLastSVGCommand = aCommand;
998 									}
999 
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;
1006 								}
1007 								else
1008 								{
1009 									const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
1010 
1011 									if(aLastSVGCommand != aCommand)
1012 									{
1013                                         aResult.append(aCommand);
1014 										aLastSVGCommand = aCommand;
1015 									}
1016 
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;
1025 								}
1026 							}
1027 						}
1028 						else
1029 						{
1030 							// straight edge
1031 							if(0 == nNextIndex)
1032 							{
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
1035 							}
1036 							else
1037 							{
1038 								const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
1039 								const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
1040 
1041 								if(bXEqual && bYEqual)
1042 								{
1043 									// point is a double point; do not export at all
1044 								}
1045 								else if(bXEqual)
1046 								{
1047 									// export as vertical line
1048 									const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
1049 
1050 									if(aLastSVGCommand != aCommand)
1051 									{
1052                                         aResult.append(aCommand);
1053 										aLastSVGCommand = aCommand;
1054 									}
1055 
1056 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1057 									aCurrentSVGPosition = aEdgeEnd;
1058 								}
1059 								else if(bYEqual)
1060 								{
1061 									// export as horizontal line
1062 									const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
1063 
1064 									if(aLastSVGCommand != aCommand)
1065 									{
1066                                         aResult.append(aCommand);
1067 										aLastSVGCommand = aCommand;
1068 									}
1069 
1070 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1071 									aCurrentSVGPosition = aEdgeEnd;
1072 								}
1073 								else
1074 								{
1075 									// export as line
1076 									const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
1077 
1078 									if(aLastSVGCommand != aCommand)
1079 									{
1080                                         aResult.append(aCommand);
1081 										aLastSVGCommand = aCommand;
1082 									}
1083 
1084 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1085 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1086 									aCurrentSVGPosition = aEdgeEnd;
1087 								}
1088 							}
1089 						}
1090 
1091 						// prepare edge start for next loop step
1092 						aEdgeStart = aEdgeEnd;
1093 					}
1094 
1095 					// close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1096 					if(aPolygon.isClosed())
1097 					{
1098                         aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
1099 					}
1100 				}
1101             }
1102 
1103             return aResult.makeStringAndClear();
1104         }
1105     }
1106 }
1107 
1108 // eof
1109