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