1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_basegfx.hxx"
26 
27 #include <basegfx/polygon/b2dpolygontools.hxx>
28 #include <basegfx/polygon/b2dpolypolygontools.hxx>
29 #include <basegfx/polygon/b2dpolygontools.hxx>
30 #include <basegfx/polygon/b2dpolypolygon.hxx>
31 #include <basegfx/matrix/b2dhommatrix.hxx>
32 #include <basegfx/matrix/b2dhommatrixtools.hxx>
33 #include <rtl/ustring.hxx>
34 #include <rtl/math.hxx>
35 
36 namespace basegfx
37 {
38 	namespace tools
39 	{
40         namespace
41         {
42             void lcl_skipSpaces(sal_Int32& 				io_rPos,
43                                 const ::rtl::OUString& 	rStr,
44                                 const sal_Int32 		nLen)
45             {
46                 while( io_rPos < nLen &&
47                        sal_Unicode(' ') == rStr[io_rPos] )
48                 {
49                     ++io_rPos;
50                 }
51             }
52 
53             void lcl_skipSpacesAndCommas(sal_Int32& 			io_rPos,
54                                          const ::rtl::OUString& rStr,
55                                          const sal_Int32 		nLen)
56             {
57                 while(io_rPos < nLen
58                       && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
59                 {
60                     ++io_rPos;
61                 }
62             }
63 
64             inline bool lcl_isOnNumberChar(const sal_Unicode aChar, bool bSignAllowed = true)
65             {
66                 const bool bPredicate( (sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
67                                        || (bSignAllowed && sal_Unicode('+') == aChar)
68                                        || (bSignAllowed && sal_Unicode('-') == aChar) );
69 
70                 return bPredicate;
71             }
72 
73             inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
74             {
75                 return lcl_isOnNumberChar(rStr[nPos],
76                                           bSignAllowed);
77             }
78 
79             bool lcl_getDoubleChar(double& 					o_fRetval,
80                                    sal_Int32& 				io_rPos,
81                                    const ::rtl::OUString& 	rStr,
82                                    const sal_Int32 			/*nLen*/)
83             {
84                 sal_Unicode aChar( rStr[io_rPos] );
85                 ::rtl::OUStringBuffer sNumberString;
86 
87                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
88                 {
89                     sNumberString.append(rStr[io_rPos]);
90                     aChar = rStr[++io_rPos];
91                 }
92 
93                 while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
94                       || sal_Unicode('.') == aChar)
95                 {
96                     sNumberString.append(rStr[io_rPos]);
97                     aChar = rStr[++io_rPos];
98                 }
99 
100                 if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
101                 {
102                     sNumberString.append(rStr[io_rPos]);
103                     aChar = rStr[++io_rPos];
104 
105                     if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
106                     {
107                         sNumberString.append(rStr[io_rPos]);
108                         aChar = rStr[++io_rPos];
109                     }
110 
111                     while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
112                     {
113                         sNumberString.append(rStr[io_rPos]);
114                         aChar = rStr[++io_rPos];
115                     }
116                 }
117 
118                 if(sNumberString.getLength())
119                 {
120                     rtl_math_ConversionStatus eStatus;
121                     o_fRetval = ::rtl::math::stringToDouble( sNumberString.makeStringAndClear(),
122                                                              (sal_Unicode)('.'),
123                                                              (sal_Unicode)(','),
124                                                              &eStatus,
125                                                              NULL );
126                     return ( eStatus == rtl_math_ConversionStatus_Ok );
127                 }
128 
129                 return false;
130             }
131 
132             bool lcl_importDoubleAndSpaces( double& 				o_fRetval,
133                                             sal_Int32& 				io_rPos,
134                                             const ::rtl::OUString& 	rStr,
135                                             const sal_Int32 		nLen )
136             {
137                 if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) )
138                     return false;
139 
140                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
141 
142                 return true;
143             }
144 
145             bool lcl_importNumberAndSpaces(sal_Int32&                o_nRetval,
146                                            sal_Int32& 				io_rPos,
147                                            const ::rtl::OUString& 	rStr,
148                                            const sal_Int32 		nLen)
149             {
150                 sal_Unicode aChar( rStr[io_rPos] );
151                 ::rtl::OUStringBuffer sNumberString;
152 
153                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
154                 {
155                     sNumberString.append(rStr[io_rPos]);
156                     aChar = rStr[++io_rPos];
157                 }
158 
159                 while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
160                 {
161                     sNumberString.append(rStr[io_rPos]);
162                     aChar = rStr[++io_rPos];
163                 }
164 
165                 if(sNumberString.getLength())
166                 {
167                     o_nRetval = sNumberString.makeStringAndClear().toInt32();
168                     lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
169 
170                     return true;
171                 }
172 
173                 return false;
174             }
175 
176             void lcl_skipNumber(sal_Int32& 				io_rPos,
177                                 const ::rtl::OUString& 	rStr,
178                                 const sal_Int32 		nLen)
179             {
180                 bool bSignAllowed(true);
181 
182                 while(io_rPos < nLen && lcl_isOnNumberChar(rStr, io_rPos, bSignAllowed))
183                 {
184                     bSignAllowed = false;
185                     ++io_rPos;
186                 }
187             }
188 
189             void lcl_skipDouble(sal_Int32& 				io_rPos,
190                                 const ::rtl::OUString& 	rStr,
191                                 const sal_Int32 		/*nLen*/)
192             {
193                 sal_Unicode aChar( rStr[io_rPos] );
194 
195                 if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
196                     aChar = rStr[++io_rPos];
197 
198                 while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
199                       || sal_Unicode('.') == aChar)
200                 {
201                     aChar = rStr[++io_rPos];
202                 }
203 
204                 if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
205                 {
206                     aChar = rStr[++io_rPos];
207 
208                     if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
209                         aChar = rStr[++io_rPos];
210 
211                     while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
212                     {
213                         aChar = rStr[++io_rPos];
214                     }
215                 }
216             }
217             void lcl_skipNumberAndSpacesAndCommas(sal_Int32& 				io_rPos,
218                                                   const ::rtl::OUString& 	rStr,
219                                                   const sal_Int32 			nLen)
220             {
221                 lcl_skipNumber(io_rPos, rStr, nLen);
222                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
223             }
224 
225 			// #100617# Allow to skip doubles, too.
226             void lcl_skipDoubleAndSpacesAndCommas(sal_Int32& 				io_rPos,
227                                                   const ::rtl::OUString& 	rStr,
228                                                   const sal_Int32 			nLen)
229             {
230                 lcl_skipDouble(io_rPos, rStr, nLen);
231                 lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
232             }
233 
234             void lcl_putNumberChar( ::rtl::OUStringBuffer& rStr,
235                                     double 		 	       fValue )
236             {
237                 rStr.append( fValue );
238             }
239 
240             void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer& rStr,
241                                              double 		        fValue,
242                                              double 		        fOldValue,
243                                              bool 			        bUseRelativeCoordinates )
244             {
245                 if( bUseRelativeCoordinates )
246                     fValue -= fOldValue;
247 
248                 const sal_Int32 aLen( rStr.getLength() );
249                 if(aLen)
250                 {
251                     if( lcl_isOnNumberChar(rStr.charAt(aLen - 1), false) &&
252                         fValue >= 0.0 )
253                     {
254                         rStr.append( sal_Unicode(' ') );
255                     }
256                 }
257 
258                 lcl_putNumberChar(rStr, fValue);
259             }
260 
261             inline sal_Unicode lcl_getCommand( sal_Char cUpperCaseCommand,
262                                                sal_Char cLowerCaseCommand,
263                                                bool 	bUseRelativeCoordinates )
264             {
265                 return bUseRelativeCoordinates ? cLowerCaseCommand : cUpperCaseCommand;
266             }
267         }
268 
269         bool importFromSvgD(B2DPolyPolygon& o_rPolyPolygon, const ::rtl::OUString& 	rSvgDStatement)
270         {
271             o_rPolyPolygon.clear();
272             const sal_Int32 nLen(rSvgDStatement.getLength());
273             sal_Int32 nPos(0);
274             bool bIsClosed(false);
275             double nLastX( 0.0 );
276             double nLastY( 0.0 );
277 			B2DPolygon aCurrPoly;
278 
279 			// skip initial whitespace
280             lcl_skipSpaces(nPos, rSvgDStatement, nLen);
281 
282             while(nPos < nLen)
283             {
284                 bool bRelative(false);
285                 bool bMoveTo(false);
286                 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
287 
288                 switch(aCurrChar)
289                 {
290                     case 'z' :
291                     case 'Z' :
292                     {
293                         nPos++;
294                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
295 
296                         // remember closed state of current polygon
297                         bIsClosed = true;
298                         break;
299                     }
300 
301                     case 'm' :
302                     case 'M' :
303                     {
304                         bMoveTo = true;
305                         // FALLTHROUGH intended
306                     }
307                     case 'l' :
308                     case 'L' :
309                     {
310                         if('m' == aCurrChar || 'l' == aCurrChar)
311 						{
312                             bRelative = true;
313 						}
314 
315                         if(bMoveTo)
316                         {
317 							// new polygon start, finish old one
318                             if(aCurrPoly.count())
319                             {
320 								// add current polygon
321 								if(bIsClosed)
322 								{
323 									closeWithGeometryChange(aCurrPoly);
324 								}
325 
326 								o_rPolyPolygon.append(aCurrPoly);
327 
328 								// reset import values
329 								bIsClosed = false;
330                                 aCurrPoly.clear();
331                             }
332                         }
333 
334                         nPos++;
335                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
336 
337                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
338                         {
339                             double nX, nY;
340 
341                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
342                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
343 
344                             if(bRelative)
345                             {
346                                 nX += nLastX;
347                                 nY += nLastY;
348                             }
349 
350                             // set last position
351                             nLastX = nX;
352                             nLastY = nY;
353 
354                             // add point
355                             aCurrPoly.append(B2DPoint(nX, nY));
356                         }
357                         break;
358                     }
359 
360                     case 'h' :
361                     {
362                         bRelative = true;
363                         // FALLTHROUGH intended
364                     }
365                     case 'H' :
366                     {
367                         nPos++;
368                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
369 
370                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
371                         {
372                             double nX, nY(nLastY);
373 
374                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
375 
376                             if(bRelative)
377 							{
378                                 nX += nLastX;
379 							}
380 
381                             // set last position
382                             nLastX = nX;
383 
384                             // add point
385                             aCurrPoly.append(B2DPoint(nX, nY));
386                         }
387                         break;
388                     }
389 
390                     case 'v' :
391                     {
392                         bRelative = true;
393                         // FALLTHROUGH intended
394                     }
395                     case 'V' :
396                     {
397                         nPos++;
398                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
399 
400                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
401                         {
402                             double nX(nLastX), nY;
403 
404                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
405 
406                             if(bRelative)
407 							{
408                                 nY += nLastY;
409 							}
410 
411                             // set last position
412                             nLastY = nY;
413 
414                             // add point
415                             aCurrPoly.append(B2DPoint(nX, nY));
416                         }
417                         break;
418                     }
419 
420                     case 's' :
421                     {
422                         bRelative = true;
423                         // FALLTHROUGH intended
424                     }
425                     case 'S' :
426                     {
427                         nPos++;
428                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
429 
430                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
431                         {
432                             double nX, nY;
433                             double nX2, nY2;
434 
435                             if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
436                             if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
437                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
438                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
439 
440                             if(bRelative)
441                             {
442                                 nX2 += nLastX;
443                                 nY2 += nLastY;
444                                 nX += nLastX;
445                                 nY += nLastY;
446                             }
447 
448 							// ensure existance of start point
449 							if(!aCurrPoly.count())
450 							{
451                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
452 							}
453 
454 							// get first control point. It's the reflection of the PrevControlPoint
455 							// of the last point. If not existent, use current point (see SVG)
456 							B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
457 							const sal_uInt32 nIndex(aCurrPoly.count() - 1);
458 
459 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
460 							{
461 								const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
462 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
463 
464 								// use mirrored previous control point
465 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
466 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
467 							}
468 
469 							// append curved edge
470 							aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
471 
472                             // set last position
473                             nLastX = nX;
474                             nLastY = nY;
475                         }
476                         break;
477                     }
478 
479                     case 'c' :
480                     {
481                         bRelative = true;
482                         // FALLTHROUGH intended
483                     }
484                     case 'C' :
485                     {
486                         nPos++;
487                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
488 
489                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
490                         {
491                             double nX, nY;
492                             double nX1, nY1;
493                             double nX2, nY2;
494 
495                             if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
496                             if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
497                             if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
498                             if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
499                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
500                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
501 
502                             if(bRelative)
503                             {
504                                 nX1 += nLastX;
505                                 nY1 += nLastY;
506                                 nX2 += nLastX;
507                                 nY2 += nLastY;
508                                 nX += nLastX;
509                                 nY += nLastY;
510                             }
511 
512 							// ensure existance of start point
513 							if(!aCurrPoly.count())
514 							{
515                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
516 							}
517 
518 							// append curved edge
519 							aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
520 
521                             // set last position
522                             nLastX = nX;
523                             nLastY = nY;
524                         }
525                         break;
526                     }
527 
528                     // #100617# quadratic beziers are imported as cubic ones
529                     case 'q' :
530                     {
531                         bRelative = true;
532                         // FALLTHROUGH intended
533                     }
534                     case 'Q' :
535                     {
536                         nPos++;
537                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
538 
539                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
540                         {
541                             double nX, nY;
542                             double nX1, nY1;
543 
544                             if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
545                             if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
546                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
547                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
548 
549                             if(bRelative)
550                             {
551                                 nX1 += nLastX;
552                                 nY1 += nLastY;
553                                 nX += nLastX;
554                                 nY += nLastY;
555                             }
556 
557                             // calculate the cubic bezier coefficients from the quadratic ones
558                             const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
559                             const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
560                             const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
561                             const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
562 
563 							// ensure existance of start point
564 							if(!aCurrPoly.count())
565 							{
566                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
567 							}
568 
569 							// append curved edge
570 							aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
571 
572                             // set last position
573                             nLastX = nX;
574                             nLastY = nY;
575                         }
576                         break;
577                     }
578 
579                     // #100617# relative quadratic beziers are imported as cubic
580                     case 't' :
581                     {
582                         bRelative = true;
583                         // FALLTHROUGH intended
584                     }
585                     case 'T' :
586                     {
587                         nPos++;
588                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
589 
590                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
591                         {
592                             double nX, nY;
593 
594                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
595                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
596 
597                             if(bRelative)
598                             {
599                                 nX += nLastX;
600                                 nY += nLastY;
601                             }
602 
603 							// ensure existance of start point
604 							if(!aCurrPoly.count())
605 							{
606                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
607 							}
608 
609 							// get first control point. It's the reflection of the PrevControlPoint
610 							// of the last point. If not existent, use current point (see SVG)
611 							B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
612 							const sal_uInt32 nIndex(aCurrPoly.count() - 1);
613 							const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
614 
615 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
616 							{
617 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
618 
619 								// use mirrored previous control point
620 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
621 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
622 							}
623 
624 							if(!aPrevControl.equal(aPrevPoint))
625 							{
626 								// there is a prev control point, and we have the already mirrored one
627 								// in aPrevControl. We also need the quadratic control point for this
628 								// new quadratic segment to calculate the 2nd cubic control point
629 								const B2DPoint aQuadControlPoint(
630 									((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
631 									((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
632 
633 								// calculate the cubic bezier coefficients from the quadratic ones.
634 								const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
635 								const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
636 
637 								// append curved edge, use mirrored cubic control point directly
638 								aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
639 							}
640 							else
641 							{
642 								// when no previous control, SVG says to use current point -> straight line.
643 								// Just add end point
644 								aCurrPoly.append(B2DPoint(nX, nY));
645 							}
646 
647                             // set last position
648                             nLastX = nX;
649                             nLastY = nY;
650                         }
651                         break;
652                     }
653 
654                     case 'a' :
655                     {
656                         bRelative = true;
657                         // FALLTHROUGH intended
658                     }
659                     case 'A' :
660                     {
661                         nPos++;
662                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
663 
664                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
665                         {
666                             double nX, nY;
667                             double fRX, fRY, fPhi;
668                             sal_Int32 bLargeArcFlag, bSweepFlag;
669 
670                             if(!lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
671                             if(!lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
672                             if(!lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
673                             if(!lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
674                             if(!lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
675                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
676                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
677 
678                             if(bRelative)
679                             {
680                                 nX += nLastX;
681                                 nY += nLastY;
682                             }
683 
684 							const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1));
685 
686                             if( nX == nLastX && nY == nLastY )
687                                 continue; // start==end -> skip according to SVG spec
688 
689                             if( fRX == 0.0 || fRY == 0.0 )
690                             {
691                                 // straight line segment according to SVG spec
692                                 aCurrPoly.append(B2DPoint(nX, nY));
693                             }
694                             else
695                             {
696                                 // normalize according to SVG spec
697                                 fRX=fabs(fRX); fRY=fabs(fRY);
698 
699                                 // from the SVG spec, appendix F.6.4
700 
701                                 // |x1'|   |cos phi   sin phi|  |(x1 - x2)/2|
702                                 // |y1'| = |-sin phi  cos phi|  |(y1 - y2)/2|
703                                 const B2DPoint p1(nLastX, nLastY);
704                                 const B2DPoint p2(nX, nY);
705                                 B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
706 
707                                 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
708 
709                                 //           ______________________________________       rx y1'
710                                 // |cx'|  + /  rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2           ry
711                                 // |cy'| =-/       rx^2y1'^2 + ry^2 x1'^2               - ry x1'
712                                 //                                                          rx
713                                 // chose + if f_A != f_S
714                                 // chose - if f_A  = f_S
715                                 B2DPoint aCenter_prime;
716                                 const double fRadicant(
717                                     (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
718                                     (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
719                                 if( fRadicant < 0.0 )
720                                 {
721                                     // no solution - according to SVG
722                                     // spec, scale up ellipse
723                                     // uniformly such that it passes
724                                     // through end points (denominator
725                                     // of radicant solved for fRY,
726                                     // with s=fRX/fRY)
727                                     const double fRatio(fRX/fRY);
728                                     const double fRadicant2(
729                                         p1_prime.getY()*p1_prime.getY() +
730                                         p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
731                                     if( fRadicant2 < 0.0 )
732                                     {
733                                         // only trivial solution, one
734                                         // of the axes 0 -> straight
735                                         // line segment according to
736                                         // SVG spec
737                                         aCurrPoly.append(B2DPoint(nX, nY));
738                                         continue;
739                                     }
740 
741                                     fRY=sqrt(fRadicant2);
742                                     fRX=fRatio*fRY;
743 
744                                     // keep center_prime forced to (0,0)
745                                 }
746                                 else
747                                 {
748                                     const double fFactor(
749                                         (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
750                                         sqrt(fRadicant));
751 
752                                     // actually calculate center_prime
753                                     aCenter_prime = B2DPoint(
754                                         fFactor*fRX*p1_prime.getY()/fRY,
755                                         -fFactor*fRY*p1_prime.getX()/fRX);
756                                 }
757 
758                                 //              +           u - v
759                                 // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
760                                 //              -        ||u|| ||v||
761 
762                                 //                  1    | (x1' - cx')/rx |
763                                 // theta1 = angle((   ), |                | )
764                                 //                  0    | (y1' - cy')/ry |
765                                 const B2DPoint aRadii(fRX,fRY);
766                                 double fTheta1(
767                                     B2DVector(1.0,0.0).angle(
768                                         (p1_prime-aCenter_prime)/aRadii));
769 
770                                 //                 |1|    |  (-x1' - cx')/rx |
771                                 // theta2 = angle( | | ,  |                  | )
772                                 //                 |0|    |  (-y1' - cy')/ry |
773                                 double fTheta2(
774                                     B2DVector(1.0,0.0).angle(
775                                         (-p1_prime-aCenter_prime)/aRadii));
776 
777                                 // map both angles to [0,2pi)
778                                 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
779                                 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
780 
781                                 // make sure the large arc is taken
782                                 // (since
783                                 // createPolygonFromEllipseSegment()
784                                 // normalizes to e.g. cw arc)
785 
786                                 // ALG: In my opinion flipping the segment only
787                                 // depends on the sweep flag. At least, this gives
788                                 // correct results forthe SVG example (see SVG doc 8.3.8 ff)
789                                 //
790                                 //const bool bFlipSegment( (bLargeArcFlag!=0) ==
791                                 //    (fmod(fTheta2+2*M_PI-fTheta1,
792                                 //          2*M_PI)<M_PI) );
793                                 const bool bFlipSegment(!bSweepFlag);
794 
795                                 if( bFlipSegment )
796                                     std::swap(fTheta1,fTheta2);
797 
798                                 // finally, create bezier polygon from this
799                                 B2DPolygon aSegment(
800                                     tools::createPolygonFromUnitEllipseSegment(
801                                         fTheta1, fTheta2 ));
802 
803                                 // transform ellipse by rotation & move to final center
804                                 aTransform = basegfx::tools::createScaleB2DHomMatrix(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);
812 
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);
820                             }
821 
822                             // set last position
823                             nLastX = nX;
824                             nLastY = nY;
825                         }
826                         break;
827                     }
828 
829                     default:
830                     {
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;
835                     }
836                 }
837             }
838 
839             if(aCurrPoly.count())
840             {
841                 // end-process last poly
842 				if(bIsClosed)
843 				{
844 					closeWithGeometryChange(aCurrPoly);
845 				}
846 
847 				o_rPolyPolygon.append(aCurrPoly);
848             }
849 
850             return true;
851         }
852 
853         bool importFromSvgPoints( B2DPolygon&            o_rPoly,
854                                   const ::rtl::OUString& rSvgPointsAttribute )
855         {
856             o_rPoly.clear();
857             const sal_Int32 nLen(rSvgPointsAttribute.getLength());
858             sal_Int32 nPos(0);
859             double nX, nY;
860 
861             // skip initial whitespace
862             lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
863 
864             while(nPos < nLen)
865             {
866                 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
867                 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
868 
869                 // add point
870                 o_rPoly.append(B2DPoint(nX, nY));
871 
872                 // skip to next number, or finish
873                 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
874             }
875 
876             return true;
877         }
878 
879         ::rtl::OUString exportToSvgD(
880 			const B2DPolyPolygon& rPolyPolygon,
881 			bool bUseRelativeCoordinates,
882 			bool bDetectQuadraticBeziers)
883         {
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
887 
888             for(sal_uInt32 i(0); i < nCount; i++)
889             {
890                 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
891                 const sal_uInt32 nPointCount(aPolygon.count());
892 
893 				if(nPointCount)
894 				{
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
899 
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;
907 
908 					for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
909 					{
910 						// prepare access to next point
911 						const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
912 						const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
913 
914 						// handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
915 						const bool bEdgeIsBezier(bPolyUsesControlPoints
916 							&& (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
917 
918 						if(bEdgeIsBezier)
919 						{
920 							// handle bezier edge
921 							const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
922 							const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
923 							bool bIsQuadraticBezier(false);
924 
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:
929 							//
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.)"
933 							//
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));
939 
940 							if(bDetectQuadraticBeziers)
941 							{
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
946 								//
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);
952 							}
953 
954 							if(bIsQuadraticBezier)
955 							{
956 								// approximately equal, export as quadratic bezier
957 								if(bSymmetricAtEdgeStart)
958 								{
959 									const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates));
960 
961 									if(aLastSVGCommand != aCommand)
962 									{
963                                         aResult.append(aCommand);
964 										aLastSVGCommand = aCommand;
965 									}
966 
967 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
968 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
969 									aLastSVGCommand = aCommand;
970 									aCurrentSVGPosition = aEdgeEnd;
971 								}
972 								else
973 								{
974 									const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
975 
976 									if(aLastSVGCommand != aCommand)
977 									{
978                                         aResult.append(aCommand);
979 										aLastSVGCommand = aCommand;
980 									}
981 
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;
988 								}
989 							}
990 							else
991 							{
992 								// export as cubic bezier
993 								if(bSymmetricAtEdgeStart)
994 								{
995 									const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
996 
997 									if(aLastSVGCommand != aCommand)
998 									{
999                                         aResult.append(aCommand);
1000 										aLastSVGCommand = aCommand;
1001 									}
1002 
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;
1009 								}
1010 								else
1011 								{
1012 									const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
1013 
1014 									if(aLastSVGCommand != aCommand)
1015 									{
1016                                         aResult.append(aCommand);
1017 										aLastSVGCommand = aCommand;
1018 									}
1019 
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;
1028 								}
1029 							}
1030 						}
1031 						else
1032 						{
1033 							// straight edge
1034 							if(0 == nNextIndex)
1035 							{
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
1038 							}
1039 							else
1040 							{
1041 								const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
1042 								const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
1043 
1044 								if(bXEqual && bYEqual)
1045 								{
1046 									// point is a double point; do not export at all
1047 								}
1048 								else if(bXEqual)
1049 								{
1050 									// export as vertical line
1051 									const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
1052 
1053 									if(aLastSVGCommand != aCommand)
1054 									{
1055                                         aResult.append(aCommand);
1056 										aLastSVGCommand = aCommand;
1057 									}
1058 
1059 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1060 									aCurrentSVGPosition = aEdgeEnd;
1061 								}
1062 								else if(bYEqual)
1063 								{
1064 									// export as horizontal line
1065 									const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
1066 
1067 									if(aLastSVGCommand != aCommand)
1068 									{
1069                                         aResult.append(aCommand);
1070 										aLastSVGCommand = aCommand;
1071 									}
1072 
1073 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1074 									aCurrentSVGPosition = aEdgeEnd;
1075 								}
1076 								else
1077 								{
1078 									// export as line
1079 									const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
1080 
1081 									if(aLastSVGCommand != aCommand)
1082 									{
1083                                         aResult.append(aCommand);
1084 										aLastSVGCommand = aCommand;
1085 									}
1086 
1087 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1088 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1089 									aCurrentSVGPosition = aEdgeEnd;
1090 								}
1091 							}
1092 						}
1093 
1094 						// prepare edge start for next loop step
1095 						aEdgeStart = aEdgeEnd;
1096 					}
1097 
1098 					// close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1099 					if(aPolygon.isClosed())
1100 					{
1101                         aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
1102 					}
1103 				}
1104             }
1105 
1106             return aResult.makeStringAndClear();
1107         }
1108     }
1109 }
1110 
1111 // eof
1112