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