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