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