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                                 const bool bFlipSegment( (bLargeArcFlag!=0) ==
786                                     (fmod(fTheta2+2*M_PI-fTheta1,
787                                           2*M_PI)<M_PI) );
788                                 if( bFlipSegment )
789                                     std::swap(fTheta1,fTheta2);
790 
791                                 // finally, create bezier polygon from this
792                                 B2DPolygon aSegment(
793                                     tools::createPolygonFromUnitEllipseSegment(
794                                         fTheta1, fTheta2 ));
795 
796                                 // transform ellipse by rotation & move to final center
797                                 aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
798                                 aTransform.translate(aCenter_prime.getX(),
799                                                      aCenter_prime.getY());
800                                 aTransform.rotate(fPhi*M_PI/180);
801                                 const B2DPoint aOffset((p1+p2)/2.0);
802                                 aTransform.translate(aOffset.getX(),
803                                                      aOffset.getY());
804                                 aSegment.transform(aTransform);
805 
806                                 // createPolygonFromEllipseSegment()
807                                 // always creates arcs that are
808                                 // positively oriented - flip polygon
809                                 // if we swapped angles above
810                                 if( bFlipSegment )
811                                     aSegment.flip();
812                                 aCurrPoly.append(aSegment);
813                             }
814 
815                             // set last position
816                             nLastX = nX;
817                             nLastY = nY;
818                         }
819                         break;
820                     }
821 
822                     default:
823                     {
824                         OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
825                         OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
826                         ++nPos;
827                         break;
828                     }
829                 }
830             }
831 
832             if(aCurrPoly.count())
833             {
834                 // end-process last poly
835 				if(bIsClosed)
836 				{
837 					closeWithGeometryChange(aCurrPoly);
838 				}
839 
840 				o_rPolyPolygon.append(aCurrPoly);
841             }
842 
843             return true;
844         }
845 
846         bool importFromSvgPoints( B2DPolygon&            o_rPoly,
847                                   const ::rtl::OUString& rSvgPointsAttribute )
848         {
849             o_rPoly.clear();
850             const sal_Int32 nLen(rSvgPointsAttribute.getLength());
851             sal_Int32 nPos(0);
852             double nX, nY;
853 
854             // skip initial whitespace
855             lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
856 
857             while(nPos < nLen)
858             {
859                 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
860                 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
861 
862                 // add point
863                 o_rPoly.append(B2DPoint(nX, nY));
864 
865                 // skip to next number, or finish
866                 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
867             }
868 
869             return true;
870         }
871 
872         ::rtl::OUString exportToSvgD(
873 			const B2DPolyPolygon& rPolyPolygon,
874 			bool bUseRelativeCoordinates,
875 			bool bDetectQuadraticBeziers)
876         {
877             const sal_uInt32 nCount(rPolyPolygon.count());
878             ::rtl::OUStringBuffer aResult;
879             B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
880 
881             for(sal_uInt32 i(0); i < nCount; i++)
882             {
883                 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
884                 const sal_uInt32 nPointCount(aPolygon.count());
885 
886 				if(nPointCount)
887 				{
888 					const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
889 					const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
890 					sal_Unicode aLastSVGCommand(' '); // last SVG command char
891 					B2DPoint aLeft, aRight; // for quadratic bezier test
892 
893 					// handle polygon start point
894 					B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
895                     aResult.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates));
896 					lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
897 					lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
898 					aLastSVGCommand =  lcl_getCommand('L', 'l', bUseRelativeCoordinates);
899 					aCurrentSVGPosition = aEdgeStart;
900 
901 					for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
902 					{
903 						// prepare access to next point
904 						const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
905 						const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
906 
907 						// handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
908 						const bool bEdgeIsBezier(bPolyUsesControlPoints
909 							&& (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
910 
911 						if(bEdgeIsBezier)
912 						{
913 							// handle bezier edge
914 							const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
915 							const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
916 							bool bIsQuadraticBezier(false);
917 
918 							// check continuity at current edge's start point. For SVG, do NOT use an
919 							// existing continuity since no 'S' or 's' statement should be written. At
920 							// import, that 'previous' control vector is not available. SVG documentation
921 							// says for interpretation:
922 							//
923 							// "(If there is no previous command or if the previous command was
924 							// not an C, c, S or s, assume the first control point is coincident
925 							// with the current point.)"
926 							//
927 							// That's what is done from our import, so avoid exporting it as first statement
928 							// is necessary.
929 							const bool bSymmetricAtEdgeStart(
930 								0 != nIndex
931 								&& CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
932 
933 							if(bDetectQuadraticBeziers)
934 							{
935 								// check for quadratic beziers - that's
936 								// the case if both control points are in
937 								// the same place when they are prolonged
938 								// to the common quadratic control point
939 								//
940 								// Left: P = (3P1 - P0) / 2
941 								// Right: P = (3P2 - P3) / 2
942 								aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
943 								aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
944 								bIsQuadraticBezier = aLeft.equal(aRight);
945 							}
946 
947 							if(bIsQuadraticBezier)
948 							{
949 								// approximately equal, export as quadratic bezier
950 								if(bSymmetricAtEdgeStart)
951 								{
952 									const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates));
953 
954 									if(aLastSVGCommand != aCommand)
955 									{
956                                         aResult.append(aCommand);
957 										aLastSVGCommand = aCommand;
958 									}
959 
960 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
961 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
962 									aLastSVGCommand = aCommand;
963 									aCurrentSVGPosition = aEdgeEnd;
964 								}
965 								else
966 								{
967 									const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
968 
969 									if(aLastSVGCommand != aCommand)
970 									{
971                                         aResult.append(aCommand);
972 										aLastSVGCommand = aCommand;
973 									}
974 
975 									lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
976 									lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
977 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
978 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
979 									aLastSVGCommand = aCommand;
980 									aCurrentSVGPosition = aEdgeEnd;
981 								}
982 							}
983 							else
984 							{
985 								// export as cubic bezier
986 								if(bSymmetricAtEdgeStart)
987 								{
988 									const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
989 
990 									if(aLastSVGCommand != aCommand)
991 									{
992                                         aResult.append(aCommand);
993 										aLastSVGCommand = aCommand;
994 									}
995 
996 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
997 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
998 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
999 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1000 									aLastSVGCommand = aCommand;
1001 									aCurrentSVGPosition = aEdgeEnd;
1002 								}
1003 								else
1004 								{
1005 									const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
1006 
1007 									if(aLastSVGCommand != aCommand)
1008 									{
1009                                         aResult.append(aCommand);
1010 										aLastSVGCommand = aCommand;
1011 									}
1012 
1013 									lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1014 									lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1015 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1016 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1017 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1018 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1019 									aLastSVGCommand = aCommand;
1020 									aCurrentSVGPosition = aEdgeEnd;
1021 								}
1022 							}
1023 						}
1024 						else
1025 						{
1026 							// straight edge
1027 							if(0 == nNextIndex)
1028 							{
1029 								// it's a closed polygon's last edge and it's not a bezier edge, so there is
1030 								// no need to write it
1031 							}
1032 							else
1033 							{
1034 								const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
1035 								const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
1036 
1037 								if(bXEqual && bYEqual)
1038 								{
1039 									// point is a double point; do not export at all
1040 								}
1041 								else if(bXEqual)
1042 								{
1043 									// export as vertical line
1044 									const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
1045 
1046 									if(aLastSVGCommand != aCommand)
1047 									{
1048                                         aResult.append(aCommand);
1049 										aLastSVGCommand = aCommand;
1050 									}
1051 
1052 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1053 									aCurrentSVGPosition = aEdgeEnd;
1054 								}
1055 								else if(bYEqual)
1056 								{
1057 									// export as horizontal line
1058 									const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
1059 
1060 									if(aLastSVGCommand != aCommand)
1061 									{
1062                                         aResult.append(aCommand);
1063 										aLastSVGCommand = aCommand;
1064 									}
1065 
1066 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1067 									aCurrentSVGPosition = aEdgeEnd;
1068 								}
1069 								else
1070 								{
1071 									// export as line
1072 									const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
1073 
1074 									if(aLastSVGCommand != aCommand)
1075 									{
1076                                         aResult.append(aCommand);
1077 										aLastSVGCommand = aCommand;
1078 									}
1079 
1080 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1081 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1082 									aCurrentSVGPosition = aEdgeEnd;
1083 								}
1084 							}
1085 						}
1086 
1087 						// prepare edge start for next loop step
1088 						aEdgeStart = aEdgeEnd;
1089 					}
1090 
1091 					// close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1092 					if(aPolygon.isClosed())
1093 					{
1094                         aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
1095 					}
1096 				}
1097             }
1098 
1099             return aResult.makeStringAndClear();
1100         }
1101     }
1102 }
1103 
1104 // eof
1105