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                                     // #123465# no need to do the old closeWithGeometryChange
324                                     // corerection on SVG polygons; this even may lead to wrong
325                                     // results e.g. for marker processing
326                                     aCurrPoly.setClosed(true);
327 								}
328 
329 								o_rPolyPolygon.append(aCurrPoly);
330 
331 								// reset import values
332 								bIsClosed = false;
333                                 aCurrPoly.clear();
334                             }
335                         }
336 
337                         nPos++;
338                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
339 
340                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
341                         {
342                             double nX, nY;
343 
344                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
345                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
346 
347                             if(bRelative)
348                             {
349                                 nX += nLastX;
350                                 nY += nLastY;
351                             }
352 
353                             // set last position
354                             nLastX = nX;
355                             nLastY = nY;
356 
357                             // add point
358                             aCurrPoly.append(B2DPoint(nX, nY));
359                         }
360                         break;
361                     }
362 
363                     case 'h' :
364                     {
365                         bRelative = true;
366                         // FALLTHROUGH intended
367                     }
368                     case 'H' :
369                     {
370                         nPos++;
371                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
372 
373                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
374                         {
375                             double nX, nY(nLastY);
376 
377                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
378 
379                             if(bRelative)
380 							{
381                                 nX += nLastX;
382 							}
383 
384                             // set last position
385                             nLastX = nX;
386 
387                             // add point
388                             aCurrPoly.append(B2DPoint(nX, nY));
389                         }
390                         break;
391                     }
392 
393                     case 'v' :
394                     {
395                         bRelative = true;
396                         // FALLTHROUGH intended
397                     }
398                     case 'V' :
399                     {
400                         nPos++;
401                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
402 
403                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
404                         {
405                             double nX(nLastX), nY;
406 
407                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
408 
409                             if(bRelative)
410 							{
411                                 nY += nLastY;
412 							}
413 
414                             // set last position
415                             nLastY = nY;
416 
417                             // add point
418                             aCurrPoly.append(B2DPoint(nX, nY));
419                         }
420                         break;
421                     }
422 
423                     case 's' :
424                     {
425                         bRelative = true;
426                         // FALLTHROUGH intended
427                     }
428                     case 'S' :
429                     {
430                         nPos++;
431                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
432 
433                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
434                         {
435                             double nX, nY;
436                             double nX2, nY2;
437 
438                             if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
439                             if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
440                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
441                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
442 
443                             if(bRelative)
444                             {
445                                 nX2 += nLastX;
446                                 nY2 += nLastY;
447                                 nX += nLastX;
448                                 nY += nLastY;
449                             }
450 
451 							// ensure existance of start point
452 							if(!aCurrPoly.count())
453 							{
454                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
455 							}
456 
457 							// get first control point. It's the reflection of the PrevControlPoint
458 							// of the last point. If not existent, use current point (see SVG)
459 							B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
460 							const sal_uInt32 nIndex(aCurrPoly.count() - 1);
461 
462 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
463 							{
464 								const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
465 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
466 
467 								// use mirrored previous control point
468 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
469 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
470 							}
471 
472 							// append curved edge
473 							aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
474 
475                             // set last position
476                             nLastX = nX;
477                             nLastY = nY;
478                         }
479                         break;
480                     }
481 
482                     case 'c' :
483                     {
484                         bRelative = true;
485                         // FALLTHROUGH intended
486                     }
487                     case 'C' :
488                     {
489                         nPos++;
490                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
491 
492                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
493                         {
494                             double nX, nY;
495                             double nX1, nY1;
496                             double nX2, nY2;
497 
498                             if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
499                             if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
500                             if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
501                             if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
502                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
503                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
504 
505                             if(bRelative)
506                             {
507                                 nX1 += nLastX;
508                                 nY1 += nLastY;
509                                 nX2 += nLastX;
510                                 nY2 += nLastY;
511                                 nX += nLastX;
512                                 nY += nLastY;
513                             }
514 
515 							// ensure existance of start point
516 							if(!aCurrPoly.count())
517 							{
518                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
519 							}
520 
521 							// append curved edge
522 							aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
523 
524                             // set last position
525                             nLastX = nX;
526                             nLastY = nY;
527                         }
528                         break;
529                     }
530 
531                     // #100617# quadratic beziers are imported as cubic ones
532                     case 'q' :
533                     {
534                         bRelative = true;
535                         // FALLTHROUGH intended
536                     }
537                     case 'Q' :
538                     {
539                         nPos++;
540                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
541 
542                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
543                         {
544                             double nX, nY;
545                             double nX1, nY1;
546 
547                             if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
548                             if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
549                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
550                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
551 
552                             if(bRelative)
553                             {
554                                 nX1 += nLastX;
555                                 nY1 += nLastY;
556                                 nX += nLastX;
557                                 nY += nLastY;
558                             }
559 
560                             // calculate the cubic bezier coefficients from the quadratic ones
561                             const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
562                             const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
563                             const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
564                             const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
565 
566 							// ensure existance of start point
567 							if(!aCurrPoly.count())
568 							{
569                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
570 							}
571 
572 							// append curved edge
573 							aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
574 
575                             // set last position
576                             nLastX = nX;
577                             nLastY = nY;
578                         }
579                         break;
580                     }
581 
582                     // #100617# relative quadratic beziers are imported as cubic
583                     case 't' :
584                     {
585                         bRelative = true;
586                         // FALLTHROUGH intended
587                     }
588                     case 'T' :
589                     {
590                         nPos++;
591                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
592 
593                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
594                         {
595                             double nX, nY;
596 
597                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
598                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
599 
600                             if(bRelative)
601                             {
602                                 nX += nLastX;
603                                 nY += nLastY;
604                             }
605 
606 							// ensure existance of start point
607 							if(!aCurrPoly.count())
608 							{
609                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
610 							}
611 
612 							// get first control point. It's the reflection of the PrevControlPoint
613 							// of the last point. If not existent, use current point (see SVG)
614 							B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
615 							const sal_uInt32 nIndex(aCurrPoly.count() - 1);
616 							const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
617 
618 							if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
619 							{
620 								const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
621 
622 								// use mirrored previous control point
623 								aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
624 								aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
625 							}
626 
627 							if(!aPrevControl.equal(aPrevPoint))
628 							{
629 								// there is a prev control point, and we have the already mirrored one
630 								// in aPrevControl. We also need the quadratic control point for this
631 								// new quadratic segment to calculate the 2nd cubic control point
632 								const B2DPoint aQuadControlPoint(
633 									((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
634 									((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
635 
636 								// calculate the cubic bezier coefficients from the quadratic ones.
637 								const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
638 								const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
639 
640 								// append curved edge, use mirrored cubic control point directly
641 								aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
642 							}
643 							else
644 							{
645 								// when no previous control, SVG says to use current point -> straight line.
646 								// Just add end point
647 								aCurrPoly.append(B2DPoint(nX, nY));
648 							}
649 
650                             // set last position
651                             nLastX = nX;
652                             nLastY = nY;
653                         }
654                         break;
655                     }
656 
657                     case 'a' :
658                     {
659                         bRelative = true;
660                         // FALLTHROUGH intended
661                     }
662                     case 'A' :
663                     {
664                         nPos++;
665                         lcl_skipSpaces(nPos, rSvgDStatement, nLen);
666 
667                         while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
668                         {
669                             double nX, nY;
670                             double fRX, fRY, fPhi;
671                             sal_Int32 bLargeArcFlag, bSweepFlag;
672 
673                             if(!lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
674                             if(!lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
675                             if(!lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
676                             if(!lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
677                             if(!lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
678                             if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
679                             if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
680 
681                             if(bRelative)
682                             {
683                                 nX += nLastX;
684                                 nY += nLastY;
685                             }
686 
687 							const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1));
688 
689                             if( nX == nLastX && nY == nLastY )
690                                 continue; // start==end -> skip according to SVG spec
691 
692                             if( fRX == 0.0 || fRY == 0.0 )
693                             {
694                                 // straight line segment according to SVG spec
695                                 aCurrPoly.append(B2DPoint(nX, nY));
696                             }
697                             else
698                             {
699                                 // normalize according to SVG spec
700                                 fRX=fabs(fRX); fRY=fabs(fRY);
701 
702                                 // from the SVG spec, appendix F.6.4
703 
704                                 // |x1'|   |cos phi   sin phi|  |(x1 - x2)/2|
705                                 // |y1'| = |-sin phi  cos phi|  |(y1 - y2)/2|
706                                 const B2DPoint p1(nLastX, nLastY);
707                                 const B2DPoint p2(nX, nY);
708                                 B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
709 
710                                 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
711 
712                                 //           ______________________________________       rx y1'
713                                 // |cx'|  + /  rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2           ry
714                                 // |cy'| =-/       rx^2y1'^2 + ry^2 x1'^2               - ry x1'
715                                 //                                                          rx
716                                 // chose + if f_A != f_S
717                                 // chose - if f_A  = f_S
718                                 B2DPoint aCenter_prime;
719                                 const double fRadicant(
720                                     (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
721                                     (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
722                                 if( fRadicant < 0.0 )
723                                 {
724                                     // no solution - according to SVG
725                                     // spec, scale up ellipse
726                                     // uniformly such that it passes
727                                     // through end points (denominator
728                                     // of radicant solved for fRY,
729                                     // with s=fRX/fRY)
730                                     const double fRatio(fRX/fRY);
731                                     const double fRadicant2(
732                                         p1_prime.getY()*p1_prime.getY() +
733                                         p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
734                                     if( fRadicant2 < 0.0 )
735                                     {
736                                         // only trivial solution, one
737                                         // of the axes 0 -> straight
738                                         // line segment according to
739                                         // SVG spec
740                                         aCurrPoly.append(B2DPoint(nX, nY));
741                                         continue;
742                                     }
743 
744                                     fRY=sqrt(fRadicant2);
745                                     fRX=fRatio*fRY;
746 
747                                     // keep center_prime forced to (0,0)
748                                 }
749                                 else
750                                 {
751                                     const double fFactor(
752                                         (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
753                                         sqrt(fRadicant));
754 
755                                     // actually calculate center_prime
756                                     aCenter_prime = B2DPoint(
757                                         fFactor*fRX*p1_prime.getY()/fRY,
758                                         -fFactor*fRY*p1_prime.getX()/fRX);
759                                 }
760 
761                                 //              +           u - v
762                                 // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
763                                 //              -        ||u|| ||v||
764 
765                                 //                  1    | (x1' - cx')/rx |
766                                 // theta1 = angle((   ), |                | )
767                                 //                  0    | (y1' - cy')/ry |
768                                 const B2DPoint aRadii(fRX,fRY);
769                                 double fTheta1(
770                                     B2DVector(1.0,0.0).angle(
771                                         (p1_prime-aCenter_prime)/aRadii));
772 
773                                 //                 |1|    |  (-x1' - cx')/rx |
774                                 // theta2 = angle( | | ,  |                  | )
775                                 //                 |0|    |  (-y1' - cy')/ry |
776                                 double fTheta2(
777                                     B2DVector(1.0,0.0).angle(
778                                         (-p1_prime-aCenter_prime)/aRadii));
779 
780                                 // map both angles to [0,2pi)
781                                 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
782                                 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
783 
784                                 // make sure the large arc is taken
785                                 // (since
786                                 // createPolygonFromEllipseSegment()
787                                 // normalizes to e.g. cw arc)
788 
789                                 // ALG: In my opinion flipping the segment only
790                                 // depends on the sweep flag. At least, this gives
791                                 // correct results forthe SVG example (see SVG doc 8.3.8 ff)
792                                 //
793                                 //const bool bFlipSegment( (bLargeArcFlag!=0) ==
794                                 //    (fmod(fTheta2+2*M_PI-fTheta1,
795                                 //          2*M_PI)<M_PI) );
796                                 const bool bFlipSegment(!bSweepFlag);
797 
798                                 if( bFlipSegment )
799                                     std::swap(fTheta1,fTheta2);
800 
801                                 // finally, create bezier polygon from this
802                                 B2DPolygon aSegment(
803                                     tools::createPolygonFromUnitEllipseSegment(
804                                         fTheta1, fTheta2 ));
805 
806                                 // transform ellipse by rotation & move to final center
807                                 aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
808                                 aTransform.translate(aCenter_prime.getX(),
809                                                      aCenter_prime.getY());
810                                 aTransform.rotate(fPhi*M_PI/180);
811                                 const B2DPoint aOffset((p1+p2)/2.0);
812                                 aTransform.translate(aOffset.getX(),
813                                                      aOffset.getY());
814                                 aSegment.transform(aTransform);
815 
816                                 // createPolygonFromEllipseSegment()
817                                 // always creates arcs that are
818                                 // positively oriented - flip polygon
819                                 // if we swapped angles above
820                                 if( bFlipSegment )
821                                     aSegment.flip();
822                                 aCurrPoly.append(aSegment);
823                             }
824 
825                             // set last position
826                             nLastX = nX;
827                             nLastY = nY;
828                         }
829                         break;
830                     }
831 
832                     default:
833                     {
834                         OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
835                         OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
836                         ++nPos;
837                         break;
838                     }
839                 }
840             }
841 
842             if(aCurrPoly.count())
843             {
844                 // end-process last poly
845 				if(bIsClosed)
846 				{
847                     // #123465# no need to do the old closeWithGeometryChange
848                     // corerection on SVG polygons; this even may lead to wrong
849                     // results e.g. for marker processing
850                     aCurrPoly.setClosed(true);
851 				}
852 
853 				o_rPolyPolygon.append(aCurrPoly);
854             }
855 
856             return true;
857         }
858 
859         bool importFromSvgPoints( B2DPolygon&            o_rPoly,
860                                   const ::rtl::OUString& rSvgPointsAttribute )
861         {
862             o_rPoly.clear();
863             const sal_Int32 nLen(rSvgPointsAttribute.getLength());
864             sal_Int32 nPos(0);
865             double nX, nY;
866 
867             // skip initial whitespace
868             lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
869 
870             while(nPos < nLen)
871             {
872                 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
873                 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
874 
875                 // add point
876                 o_rPoly.append(B2DPoint(nX, nY));
877 
878                 // skip to next number, or finish
879                 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
880             }
881 
882             return true;
883         }
884 
885         ::rtl::OUString exportToSvgD(
886 			const B2DPolyPolygon& rPolyPolygon,
887 			bool bUseRelativeCoordinates,
888 			bool bDetectQuadraticBeziers)
889         {
890             const sal_uInt32 nCount(rPolyPolygon.count());
891             ::rtl::OUStringBuffer aResult;
892             B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
893 
894             for(sal_uInt32 i(0); i < nCount; i++)
895             {
896                 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
897                 const sal_uInt32 nPointCount(aPolygon.count());
898 
899 				if(nPointCount)
900 				{
901 					const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
902 					const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
903 					sal_Unicode aLastSVGCommand(' '); // last SVG command char
904 					B2DPoint aLeft, aRight; // for quadratic bezier test
905 
906 					// handle polygon start point
907 					B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
908                     aResult.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates));
909 					lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
910 					lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
911 					aLastSVGCommand =  lcl_getCommand('L', 'l', bUseRelativeCoordinates);
912 					aCurrentSVGPosition = aEdgeStart;
913 
914 					for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
915 					{
916 						// prepare access to next point
917 						const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
918 						const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
919 
920 						// handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
921 						const bool bEdgeIsBezier(bPolyUsesControlPoints
922 							&& (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
923 
924 						if(bEdgeIsBezier)
925 						{
926 							// handle bezier edge
927 							const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
928 							const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
929 							bool bIsQuadraticBezier(false);
930 
931 							// check continuity at current edge's start point. For SVG, do NOT use an
932 							// existing continuity since no 'S' or 's' statement should be written. At
933 							// import, that 'previous' control vector is not available. SVG documentation
934 							// says for interpretation:
935 							//
936 							// "(If there is no previous command or if the previous command was
937 							// not an C, c, S or s, assume the first control point is coincident
938 							// with the current point.)"
939 							//
940 							// That's what is done from our import, so avoid exporting it as first statement
941 							// is necessary.
942 							const bool bSymmetricAtEdgeStart(
943 								0 != nIndex
944 								&& CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
945 
946 							if(bDetectQuadraticBeziers)
947 							{
948 								// check for quadratic beziers - that's
949 								// the case if both control points are in
950 								// the same place when they are prolonged
951 								// to the common quadratic control point
952 								//
953 								// Left: P = (3P1 - P0) / 2
954 								// Right: P = (3P2 - P3) / 2
955 								aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
956 								aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
957 								bIsQuadraticBezier = aLeft.equal(aRight);
958 							}
959 
960 							if(bIsQuadraticBezier)
961 							{
962 								// approximately equal, export as quadratic bezier
963 								if(bSymmetricAtEdgeStart)
964 								{
965 									const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates));
966 
967 									if(aLastSVGCommand != aCommand)
968 									{
969                                         aResult.append(aCommand);
970 										aLastSVGCommand = aCommand;
971 									}
972 
973 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
974 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
975 									aLastSVGCommand = aCommand;
976 									aCurrentSVGPosition = aEdgeEnd;
977 								}
978 								else
979 								{
980 									const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
981 
982 									if(aLastSVGCommand != aCommand)
983 									{
984                                         aResult.append(aCommand);
985 										aLastSVGCommand = aCommand;
986 									}
987 
988 									lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
989 									lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
990 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
991 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
992 									aLastSVGCommand = aCommand;
993 									aCurrentSVGPosition = aEdgeEnd;
994 								}
995 							}
996 							else
997 							{
998 								// export as cubic bezier
999 								if(bSymmetricAtEdgeStart)
1000 								{
1001 									const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
1002 
1003 									if(aLastSVGCommand != aCommand)
1004 									{
1005                                         aResult.append(aCommand);
1006 										aLastSVGCommand = aCommand;
1007 									}
1008 
1009 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1010 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1011 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1012 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1013 									aLastSVGCommand = aCommand;
1014 									aCurrentSVGPosition = aEdgeEnd;
1015 								}
1016 								else
1017 								{
1018 									const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
1019 
1020 									if(aLastSVGCommand != aCommand)
1021 									{
1022                                         aResult.append(aCommand);
1023 										aLastSVGCommand = aCommand;
1024 									}
1025 
1026 									lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1027 									lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1028 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1029 									lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1030 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1031 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1032 									aLastSVGCommand = aCommand;
1033 									aCurrentSVGPosition = aEdgeEnd;
1034 								}
1035 							}
1036 						}
1037 						else
1038 						{
1039 							// straight edge
1040 							if(0 == nNextIndex)
1041 							{
1042 								// it's a closed polygon's last edge and it's not a bezier edge, so there is
1043 								// no need to write it
1044 							}
1045 							else
1046 							{
1047 								const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
1048 								const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
1049 
1050 								if(bXEqual && bYEqual)
1051 								{
1052 									// point is a double point; do not export at all
1053 								}
1054 								else if(bXEqual)
1055 								{
1056 									// export as vertical line
1057 									const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
1058 
1059 									if(aLastSVGCommand != aCommand)
1060 									{
1061                                         aResult.append(aCommand);
1062 										aLastSVGCommand = aCommand;
1063 									}
1064 
1065 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1066 									aCurrentSVGPosition = aEdgeEnd;
1067 								}
1068 								else if(bYEqual)
1069 								{
1070 									// export as horizontal line
1071 									const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
1072 
1073 									if(aLastSVGCommand != aCommand)
1074 									{
1075                                         aResult.append(aCommand);
1076 										aLastSVGCommand = aCommand;
1077 									}
1078 
1079 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1080 									aCurrentSVGPosition = aEdgeEnd;
1081 								}
1082 								else
1083 								{
1084 									// export as line
1085 									const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
1086 
1087 									if(aLastSVGCommand != aCommand)
1088 									{
1089                                         aResult.append(aCommand);
1090 										aLastSVGCommand = aCommand;
1091 									}
1092 
1093 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1094 									lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1095 									aCurrentSVGPosition = aEdgeEnd;
1096 								}
1097 							}
1098 						}
1099 
1100 						// prepare edge start for next loop step
1101 						aEdgeStart = aEdgeEnd;
1102 					}
1103 
1104 					// close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1105 					if(aPolygon.isClosed())
1106 					{
1107                         aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
1108 					}
1109 				}
1110             }
1111 
1112             return aResult.makeStringAndClear();
1113         }
1114     }
1115 }
1116 
1117 // eof
1118