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