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