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 #include <stringconversiontools.hxx>
36 
37 namespace basegfx
38 {
39     namespace tools
40     {
operator <(const PointIndex & rComp) const41         bool PointIndex::operator<(const PointIndex& rComp) const
42         {
43             if(rComp.getPolygonIndex() == getPolygonIndex())
44             {
45                 return rComp.getPointIndex() < getPointIndex();
46             }
47 
48             return rComp.getPolygonIndex() < getPolygonIndex();
49         }
50 
importFromSvgD(B2DPolyPolygon & o_rPolyPolygon,const::rtl::OUString & rSvgDStatement,bool bHandleRelativeNextPointCompatible,PointIndexSet * pHelpPointIndexSet)51         bool importFromSvgD(
52             B2DPolyPolygon& o_rPolyPolygon,
53             const ::rtl::OUString& rSvgDStatement,
54             bool bHandleRelativeNextPointCompatible,
55             PointIndexSet* pHelpPointIndexSet)
56         {
57             o_rPolyPolygon.clear();
58             const sal_Int32 nLen(rSvgDStatement.getLength());
59             sal_Int32 nPos(0);
60             double nLastX( 0.0 );
61             double nLastY( 0.0 );
62             B2DPolygon aCurrPoly;
63 
64             // skip initial whitespace
65             ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
66 
67             while(nPos < nLen)
68             {
69                 bool bRelative(false);
70                 const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
71 
72                 if(o_rPolyPolygon.count() && !aCurrPoly.count() && !('m' == aCurrChar || 'M' == aCurrChar))
73                 {
74                     // we have a new sub-polygon starting, but without a 'moveto' command.
75                     // this requires to add the current point as start point to the polygon
76                     // (see SVG1.1 8.3.3 The "closepath" command)
77                     aCurrPoly.append(B2DPoint(nLastX, nLastY));
78                 }
79 
80                 switch(aCurrChar)
81                 {
82                     case 'z' :
83                     case 'Z' :
84                     {
85                         // consume CurrChar and whitespace
86                         nPos++;
87                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
88 
89                         // create closed polygon and reset import values
90                         if(aCurrPoly.count())
91                         {
92                             if(!bHandleRelativeNextPointCompatible)
93                             {
94                                 // SVG defines that "the next subpath starts at the
95                                 // same initial point as the current subpath", so set the
96                                 // current point if we do not need to be compatible
97                                 nLastX = aCurrPoly.getB2DPoint(0).getX();
98                                 nLastY = aCurrPoly.getB2DPoint(0).getY();
99                             }
100 
101                             aCurrPoly.setClosed(true);
102                             o_rPolyPolygon.append(aCurrPoly);
103                             aCurrPoly.clear();
104                         }
105 
106                         break;
107                     }
108 
109                     case 'm' :
110                     case 'M' :
111                     {
112                         // create non-closed polygon and reset import values
113                         if(aCurrPoly.count())
114                         {
115                             o_rPolyPolygon.append(aCurrPoly);
116                             aCurrPoly.clear();
117                         }
118 
119                         // FALLTHROUGH intended to add coordinate data as 1st point of new polygon
120                     }
121                     case 'l' :
122                     case 'L' :
123                     {
124                         if('m' == aCurrChar || 'l' == aCurrChar)
125                         {
126                             bRelative = true;
127                         }
128 
129                         // consume CurrChar and whitespace
130                         nPos++;
131                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
132 
133                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
134                         {
135                             double nX, nY;
136 
137                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
138                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
139 
140                             if(bRelative)
141                             {
142                                 nX += nLastX;
143                                 nY += nLastY;
144                             }
145 
146                             // set last position
147                             nLastX = nX;
148                             nLastY = nY;
149 
150                             // add point
151                             aCurrPoly.append(B2DPoint(nX, nY));
152                         }
153                         break;
154                     }
155 
156                     case 'h' :
157                     {
158                         bRelative = true;
159                         // FALLTHROUGH intended
160                     }
161                     case 'H' :
162                     {
163                         nPos++;
164                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
165 
166                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
167                         {
168                             double nX, nY(nLastY);
169 
170                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
171 
172                             if(bRelative)
173                             {
174                                 nX += nLastX;
175                             }
176 
177                             // set last position
178                             nLastX = nX;
179 
180                             // add point
181                             aCurrPoly.append(B2DPoint(nX, nY));
182                         }
183                         break;
184                     }
185 
186                     case 'v' :
187                     {
188                         bRelative = true;
189                         // FALLTHROUGH intended
190                     }
191                     case 'V' :
192                     {
193                         nPos++;
194                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
195 
196                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
197                         {
198                             double nX(nLastX), nY;
199 
200                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
201 
202                             if(bRelative)
203                             {
204                                 nY += nLastY;
205                             }
206 
207                             // set last position
208                             nLastY = nY;
209 
210                             // add point
211                             aCurrPoly.append(B2DPoint(nX, nY));
212                         }
213                         break;
214                     }
215 
216                     case 's' :
217                     {
218                         bRelative = true;
219                         // FALLTHROUGH intended
220                     }
221                     case 'S' :
222                     {
223                         nPos++;
224                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
225 
226                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
227                         {
228                             double nX, nY;
229                             double nX2, nY2;
230 
231                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
232                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
233                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
234                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
235 
236                             if(bRelative)
237                             {
238                                 nX2 += nLastX;
239                                 nY2 += nLastY;
240                                 nX += nLastX;
241                                 nY += nLastY;
242                             }
243 
244                             // ensure existence of start point
245                             if(!aCurrPoly.count())
246                             {
247                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
248                             }
249 
250                             // get first control point. It's the reflection of the PrevControlPoint
251                             // of the last point. If not existent, use current point (see SVG)
252                             B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
253                             const sal_uInt32 nIndex(aCurrPoly.count() - 1);
254 
255                             if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
256                             {
257                                 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
258                                 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
259 
260                                 // use mirrored previous control point
261                                 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
262                                 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
263                             }
264 
265                             // append curved edge
266                             aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
267 
268                             // set last position
269                             nLastX = nX;
270                             nLastY = nY;
271                         }
272                         break;
273                     }
274 
275                     case 'c' :
276                     {
277                         bRelative = true;
278                         // FALLTHROUGH intended
279                     }
280                     case 'C' :
281                     {
282                         nPos++;
283                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
284 
285                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
286                         {
287                             double nX, nY;
288                             double nX1, nY1;
289                             double nX2, nY2;
290 
291                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
292                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
293                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
294                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
295                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
296                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
297 
298                             if(bRelative)
299                             {
300                                 nX1 += nLastX;
301                                 nY1 += nLastY;
302                                 nX2 += nLastX;
303                                 nY2 += nLastY;
304                                 nX += nLastX;
305                                 nY += nLastY;
306                             }
307 
308                             // ensure existence of start point
309                             if(!aCurrPoly.count())
310                             {
311                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
312                             }
313 
314                             // append curved edge
315                             aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
316 
317                             // set last position
318                             nLastX = nX;
319                             nLastY = nY;
320                         }
321                         break;
322                     }
323 
324                     // #100617# quadratic beziers are imported as cubic ones
325                     case 'q' :
326                     {
327                         bRelative = true;
328                         // FALLTHROUGH intended
329                     }
330                     case 'Q' :
331                     {
332                         nPos++;
333                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
334 
335                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
336                         {
337                             double nX, nY;
338                             double nX1, nY1;
339 
340                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
341                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
342                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
343                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
344 
345                             if(bRelative)
346                             {
347                                 nX1 += nLastX;
348                                 nY1 += nLastY;
349                                 nX += nLastX;
350                                 nY += nLastY;
351                             }
352 
353                             // calculate the cubic bezier coefficients from the quadratic ones
354                             const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
355                             const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
356                             const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
357                             const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
358 
359                             // ensure existence of start point
360                             if(!aCurrPoly.count())
361                             {
362                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
363                             }
364 
365                             // append curved edge
366                             aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
367 
368                             // set last position
369                             nLastX = nX;
370                             nLastY = nY;
371                         }
372                         break;
373                     }
374 
375                     // #100617# relative quadratic beziers are imported as cubic
376                     case 't' :
377                     {
378                         bRelative = true;
379                         // FALLTHROUGH intended
380                     }
381                     case 'T' :
382                     {
383                         nPos++;
384                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
385 
386                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
387                         {
388                             double nX, nY;
389 
390                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
391                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
392 
393                             if(bRelative)
394                             {
395                                 nX += nLastX;
396                                 nY += nLastY;
397                             }
398 
399                             // ensure existence of start point
400                             if(!aCurrPoly.count())
401                             {
402                                 aCurrPoly.append(B2DPoint(nLastX, nLastY));
403                             }
404 
405                             // get first control point. It's the reflection of the PrevControlPoint
406                             // of the last point. If not existent, use current point (see SVG)
407                             B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
408                             const sal_uInt32 nIndex(aCurrPoly.count() - 1);
409                             const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
410 
411                             if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
412                             {
413                                 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
414 
415                                 // use mirrored previous control point
416                                 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
417                                 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
418                             }
419 
420                             if(!aPrevControl.equal(aPrevPoint))
421                             {
422                                 // there is a prev control point, and we have the already mirrored one
423                                 // in aPrevControl. We also need the quadratic control point for this
424                                 // new quadratic segment to calculate the 2nd cubic control point
425                                 const B2DPoint aQuadControlPoint(
426                                     ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
427                                     ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
428 
429                                 // calculate the cubic bezier coefficients from the quadratic ones.
430                                 const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
431                                 const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
432 
433                                 // append curved edge, use mirrored cubic control point directly
434                                 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
435                             }
436                             else
437                             {
438                                 // when no previous control, SVG says to use current point -> straight line.
439                                 // Just add end point
440                                 aCurrPoly.append(B2DPoint(nX, nY));
441                             }
442 
443                             // set last position
444                             nLastX = nX;
445                             nLastY = nY;
446                         }
447                         break;
448                     }
449 
450                     case 'a' :
451                     {
452                         bRelative = true;
453                         // FALLTHROUGH intended
454                     }
455                     case 'A' :
456                     {
457                         nPos++;
458                         ::basegfx::internal::lcl_skipSpaces(nPos, rSvgDStatement, nLen);
459 
460                         while(nPos < nLen && ::basegfx::internal::lcl_isOnNumberChar(rSvgDStatement, nPos))
461                         {
462                             double nX, nY;
463                             double fRX, fRY, fPhi;
464                             sal_Int32 bLargeArcFlag, bSweepFlag;
465 
466                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
467                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
468                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
469                             if(!::basegfx::internal::lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
470                             if(!::basegfx::internal::lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
471                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
472                             if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
473 
474                             if(bRelative)
475                             {
476                                 nX += nLastX;
477                                 nY += nLastY;
478                             }
479 
480                             const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1));
481 
482                             if( nX == nLastX && nY == nLastY )
483                                 continue; // start==end -> skip according to SVG spec
484 
485                             if( fRX == 0.0 || fRY == 0.0 )
486                             {
487                                 // straight line segment according to SVG spec
488                                 aCurrPoly.append(B2DPoint(nX, nY));
489                             }
490                             else
491                             {
492                                 // normalize according to SVG spec
493                                 fRX=fabs(fRX); fRY=fabs(fRY);
494 
495                                 // from the SVG spec, appendix F.6.4
496 
497                                 // |x1'|   |cos phi   sin phi|  |(x1 - x2)/2|
498                                 // |y1'| = |-sin phi  cos phi|  |(y1 - y2)/2|
499                                 const B2DPoint p1(nLastX, nLastY);
500                                 const B2DPoint p2(nX, nY);
501                                 B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
502 
503                                 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
504 
505                                 //           ______________________________________       rx y1'
506                                 // |cx'|  + /  rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2           ry
507                                 // |cy'| =-/       rx^2y1'^2 + ry^2 x1'^2               - ry x1'
508                                 //                                                          rx
509                                 // chose + if f_A != f_S
510                                 // chose - if f_A  = f_S
511                                 B2DPoint aCenter_prime;
512                                 const double fRadicant(
513                                     (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
514                                     (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
515                                 if( fRadicant < 0.0 )
516                                 {
517                                     // no solution - according to SVG
518                                     // spec, scale up ellipse
519                                     // uniformly such that it passes
520                                     // through end points (denominator
521                                     // of radicant solved for fRY,
522                                     // with s=fRX/fRY)
523                                     const double fRatio(fRX/fRY);
524                                     const double fRadicant2(
525                                         p1_prime.getY()*p1_prime.getY() +
526                                         p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
527                                     if( fRadicant2 < 0.0 )
528                                     {
529                                         // only trivial solution, one
530                                         // of the axes 0 -> straight
531                                         // line segment according to
532                                         // SVG spec
533                                         aCurrPoly.append(B2DPoint(nX, nY));
534                                         continue;
535                                     }
536 
537                                     fRY=sqrt(fRadicant2);
538                                     fRX=fRatio*fRY;
539 
540                                     // keep center_prime forced to (0,0)
541                                 }
542                                 else
543                                 {
544                                     const double fFactor(
545                                         (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
546                                         sqrt(fRadicant));
547 
548                                     // actually calculate center_prime
549                                     aCenter_prime = B2DPoint(
550                                         fFactor*fRX*p1_prime.getY()/fRY,
551                                         -fFactor*fRY*p1_prime.getX()/fRX);
552                                 }
553 
554                                 //              +           u - v
555                                 // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
556                                 //              -        ||u|| ||v||
557 
558                                 //                  1    | (x1' - cx')/rx |
559                                 // theta1 = angle((   ), |                | )
560                                 //                  0    | (y1' - cy')/ry |
561                                 const B2DPoint aRadii(fRX,fRY);
562                                 double fTheta1(
563                                     B2DVector(1.0,0.0).angle(
564                                         (p1_prime-aCenter_prime)/aRadii));
565 
566                                 //                 |1|    |  (-x1' - cx')/rx |
567                                 // theta2 = angle( | | ,  |                  | )
568                                 //                 |0|    |  (-y1' - cy')/ry |
569                                 double fTheta2(
570                                     B2DVector(1.0,0.0).angle(
571                                         (-p1_prime-aCenter_prime)/aRadii));
572 
573                                 // map both angles to [0,2pi)
574                                 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
575                                 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
576 
577                                 // make sure the large arc is taken
578                                 // (since
579                                 // createPolygonFromEllipseSegment()
580                                 // normalizes to e.g. cw arc)
581 
582                                 // ALG: In my opinion flipping the segment only
583                                 // depends on the sweep flag. At least, this gives
584                                 // correct results forthe SVG example (see SVG doc 8.3.8 ff)
585                                 //
586                                 //const bool bFlipSegment( (bLargeArcFlag!=0) ==
587                                 //    (fmod(fTheta2+2*M_PI-fTheta1,
588                                 //          2*M_PI)<M_PI) );
589                                 const bool bFlipSegment(!bSweepFlag);
590 
591                                 if( bFlipSegment )
592                                     std::swap(fTheta1,fTheta2);
593 
594                                 // finally, create bezier polygon from this
595                                 B2DPolygon aSegment(
596                                     tools::createPolygonFromUnitEllipseSegment(
597                                         fTheta1, fTheta2 ));
598 
599                                 // transform ellipse by rotation & move to final center
600                                 aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
601                                 aTransform.translate(aCenter_prime.getX(),
602                                                      aCenter_prime.getY());
603                                 aTransform.rotate(fPhi*M_PI/180);
604                                 const B2DPoint aOffset((p1+p2)/2.0);
605                                 aTransform.translate(aOffset.getX(),
606                                                      aOffset.getY());
607                                 aSegment.transform(aTransform);
608 
609                                 // createPolygonFromEllipseSegment()
610                                 // always creates arcs that are
611                                 // positively oriented - flip polygon
612                                 // if we swapped angles above
613                                 if( bFlipSegment )
614                                     aSegment.flip();
615 
616                                 // remember PointIndex of evtl. added pure helper points
617                                 sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
618                                 aCurrPoly.append(aSegment);
619 
620                                 // if asked for, mark pure helper points by adding them to the index list of
621                                 // helper points
622                                 if(pHelpPointIndexSet && aCurrPoly.count() > 1)
623                                 {
624                                     const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
625 
626                                     for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
627                                     {
628                                         pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
629                                     }
630                                 }
631                             }
632 
633                             // set last position
634                             nLastX = nX;
635                             nLastY = nY;
636                         }
637                         break;
638                     }
639 
640                     default:
641                     {
642                         OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
643                         OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
644                         ++nPos;
645                         break;
646                     }
647                 }
648             }
649 
650             // if there is polygon data, create non-closed polygon
651             if(aCurrPoly.count())
652             {
653                 o_rPolyPolygon.append(aCurrPoly);
654             }
655 
656             return true;
657         }
658 
importFromSvgPoints(B2DPolygon & o_rPoly,const::rtl::OUString & rSvgPointsAttribute)659         bool importFromSvgPoints( B2DPolygon&            o_rPoly,
660                                   const ::rtl::OUString& rSvgPointsAttribute )
661         {
662             o_rPoly.clear();
663             const sal_Int32 nLen(rSvgPointsAttribute.getLength());
664             sal_Int32 nPos(0);
665             double nX, nY;
666 
667             // skip initial whitespace
668             ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
669 
670             while(nPos < nLen)
671             {
672                 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
673                 if(!::basegfx::internal::lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
674 
675                 // add point
676                 o_rPoly.append(B2DPoint(nX, nY));
677 
678                 // skip to next number, or finish
679                 ::basegfx::internal::lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
680             }
681 
682             return true;
683         }
684 
exportToSvgPoints(const B2DPolygon & rPoly)685         ::rtl::OUString exportToSvgPoints( const B2DPolygon& rPoly )
686         {
687             OSL_ENSURE(!rPoly.areControlPointsUsed(), "exportToSvgPoints: Only non-bezier polygons allowed (!)");
688             const sal_uInt32 nPointCount(rPoly.count());
689             ::rtl::OUStringBuffer aResult;
690 
691             for(sal_uInt32 a(0); a < nPointCount; a++)
692             {
693                 const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
694 
695                 if(a)
696                 {
697                     aResult.append(sal_Unicode(' '));
698                 }
699 
700                 ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getX());
701                 aResult.append(sal_Unicode(','));
702                 ::basegfx::internal::lcl_putNumberChar(aResult, aPoint.getY());
703             }
704 
705             return aResult.makeStringAndClear();
706         }
707 
exportToSvgD(const B2DPolyPolygon & rPolyPolygon,bool bUseRelativeCoordinates,bool bDetectQuadraticBeziers,bool bHandleRelativeNextPointCompatible)708         ::rtl::OUString exportToSvgD(
709             const B2DPolyPolygon& rPolyPolygon,
710             bool bUseRelativeCoordinates,
711             bool bDetectQuadraticBeziers,
712             bool bHandleRelativeNextPointCompatible)
713         {
714             const sal_uInt32 nCount(rPolyPolygon.count());
715             ::rtl::OUStringBuffer aResult;
716             B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
717 
718             for(sal_uInt32 i(0); i < nCount; i++)
719             {
720                 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
721                 const sal_uInt32 nPointCount(aPolygon.count());
722 
723                 if(nPointCount)
724                 {
725                     const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
726                     const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
727                     sal_Unicode aLastSVGCommand(' '); // last SVG command char
728                     B2DPoint aLeft, aRight; // for quadratic bezier test
729 
730                     // handle polygon start point
731                     B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
732                     bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
733 
734                     if(bHandleRelativeNextPointCompatible)
735                     {
736                         // To get around the error that the start point for the next polygon is the
737                         // start point of the current one (and not the last as it was handled up to now)
738                         // do force to write an absolute 'M' command as start for the next polygon
739                         bUseRelativeCoordinatesForFirstPoint = false;
740                     }
741 
742                     // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
743                     aResult.append(::basegfx::internal::lcl_getCommand('M', 'm', bUseRelativeCoordinatesForFirstPoint));
744                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint);
745                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint);
746                     aLastSVGCommand =  ::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinatesForFirstPoint);
747                     aCurrentSVGPosition = aEdgeStart;
748 
749                     for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
750                     {
751                         // prepare access to next point
752                         const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
753                         const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
754 
755                         // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
756                         const bool bEdgeIsBezier(bPolyUsesControlPoints
757                             && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
758 
759                         if(bEdgeIsBezier)
760                         {
761                             // handle bezier edge
762                             const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
763                             const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
764                             bool bIsQuadraticBezier(false);
765 
766                             // check continuity at current edge's start point. For SVG, do NOT use an
767                             // existing continuity since no 'S' or 's' statement should be written. At
768                             // import, that 'previous' control vector is not available. SVG documentation
769                             // says for interpretation:
770                             //
771                             // "(If there is no previous command or if the previous command was
772                             // not an C, c, S or s, assume the first control point is coincident
773                             // with the current point.)"
774                             //
775                             // That's what is done from our import, so avoid exporting it as first statement
776                             // is necessary.
777                             const bool bSymmetricAtEdgeStart(
778                                 0 != nIndex
779                                 && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
780 
781                             if(bDetectQuadraticBeziers)
782                             {
783                                 // check for quadratic beziers - that's
784                                 // the case if both control points are in
785                                 // the same place when they are prolonged
786                                 // to the common quadratic control point
787                                 //
788                                 // Left: P = (3P1 - P0) / 2
789                                 // Right: P = (3P2 - P3) / 2
790                                 aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
791                                 aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
792                                 bIsQuadraticBezier = aLeft.equal(aRight);
793                             }
794 
795                             if(bIsQuadraticBezier)
796                             {
797                                 // approximately equal, export as quadratic bezier
798                                 if(bSymmetricAtEdgeStart)
799                                 {
800                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('T', 't', bUseRelativeCoordinates));
801 
802                                     if(aLastSVGCommand != aCommand)
803                                     {
804                                         aResult.append(aCommand);
805                                         aLastSVGCommand = aCommand;
806                                     }
807 
808                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
809                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
810                                     aLastSVGCommand = aCommand;
811                                     aCurrentSVGPosition = aEdgeEnd;
812                                 }
813                                 else
814                                 {
815                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
816 
817                                     if(aLastSVGCommand != aCommand)
818                                     {
819                                         aResult.append(aCommand);
820                                         aLastSVGCommand = aCommand;
821                                     }
822 
823                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
824                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
825                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
826                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
827                                     aLastSVGCommand = aCommand;
828                                     aCurrentSVGPosition = aEdgeEnd;
829                                 }
830                             }
831                             else
832                             {
833                                 // export as cubic bezier
834                                 if(bSymmetricAtEdgeStart)
835                                 {
836                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('S', 's', bUseRelativeCoordinates));
837 
838                                     if(aLastSVGCommand != aCommand)
839                                     {
840                                         aResult.append(aCommand);
841                                         aLastSVGCommand = aCommand;
842                                     }
843 
844                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
845                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
846                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
847                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
848                                     aLastSVGCommand = aCommand;
849                                     aCurrentSVGPosition = aEdgeEnd;
850                                 }
851                                 else
852                                 {
853                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('C', 'c', bUseRelativeCoordinates));
854 
855                                     if(aLastSVGCommand != aCommand)
856                                     {
857                                         aResult.append(aCommand);
858                                         aLastSVGCommand = aCommand;
859                                     }
860 
861                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
862                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
863                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
864                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
865                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
866                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
867                                     aLastSVGCommand = aCommand;
868                                     aCurrentSVGPosition = aEdgeEnd;
869                                 }
870                             }
871                         }
872                         else
873                         {
874                             // straight edge
875                             if(0 == nNextIndex)
876                             {
877                                 // it's a closed polygon's last edge and it's not a bezier edge, so there is
878                                 // no need to write it
879                             }
880                             else
881                             {
882                                 const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
883                                 const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
884 
885                                 if(bXEqual && bYEqual)
886                                 {
887                                     // point is a double point; do not export at all
888                                 }
889                                 else if(bXEqual)
890                                 {
891                                     // export as vertical line
892                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('V', 'v', bUseRelativeCoordinates));
893 
894                                     if(aLastSVGCommand != aCommand)
895                                     {
896                                         aResult.append(aCommand);
897                                         aLastSVGCommand = aCommand;
898                                     }
899 
900                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
901                                     aCurrentSVGPosition = aEdgeEnd;
902                                 }
903                                 else if(bYEqual)
904                                 {
905                                     // export as horizontal line
906                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('H', 'h', bUseRelativeCoordinates));
907 
908                                     if(aLastSVGCommand != aCommand)
909                                     {
910                                         aResult.append(aCommand);
911                                         aLastSVGCommand = aCommand;
912                                     }
913 
914                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
915                                     aCurrentSVGPosition = aEdgeEnd;
916                                 }
917                                 else
918                                 {
919                                     // export as line
920                                     const sal_Unicode aCommand(::basegfx::internal::lcl_getCommand('L', 'l', bUseRelativeCoordinates));
921 
922                                     if(aLastSVGCommand != aCommand)
923                                     {
924                                         aResult.append(aCommand);
925                                         aLastSVGCommand = aCommand;
926                                     }
927 
928                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
929                                     ::basegfx::internal::lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
930                                     aCurrentSVGPosition = aEdgeEnd;
931                                 }
932                             }
933                         }
934 
935                         // prepare edge start for next loop step
936                         aEdgeStart = aEdgeEnd;
937                     }
938 
939                     // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
940                     if(aPolygon.isClosed())
941                     {
942                         aResult.append(::basegfx::internal::lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
943                     }
944 
945                     if(!bHandleRelativeNextPointCompatible)
946                     {
947                         // SVG defines that "the next subpath starts at the same initial point as the current subpath",
948                         // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
949                         aCurrentSVGPosition = aPolygon.getB2DPoint(0);
950                     }
951                 }
952             }
953 
954             return aResult.makeStringAndClear();
955         }
956     }
957 }
958 
959 // eof
960