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 5914 : void lcl_skipSpaces(sal_Int32& io_rPos,
36 : const OUString& rStr,
37 : const sal_Int32 nLen)
38 : {
39 17836 : while( io_rPos < nLen &&
40 5862 : sal_Unicode(' ') == rStr[io_rPos] )
41 : {
42 146 : ++io_rPos;
43 : }
44 5914 : }
45 :
46 10128 : void lcl_skipSpacesAndCommas(sal_Int32& io_rPos,
47 : const OUString& rStr,
48 : const sal_Int32 nLen)
49 : {
50 47160 : while(io_rPos < nLen
51 23570 : && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
52 : {
53 3334 : ++io_rPos;
54 : }
55 10128 : }
56 :
57 16622 : 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 16622 : || (sal_Unicode('.') == aChar) );
63 :
64 16622 : return bPredicate;
65 : }
66 :
67 10968 : inline bool lcl_isOnNumberChar(const OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
68 : {
69 10968 : return lcl_isOnNumberChar(rStr[nPos],
70 21936 : bSignAllowed);
71 : }
72 :
73 10128 : bool lcl_getDoubleChar(double& o_fRetval,
74 : sal_Int32& io_rPos,
75 : const OUString& rStr)
76 : {
77 10128 : sal_Unicode aChar( rStr[io_rPos] );
78 10128 : OUStringBuffer sNumberString;
79 10128 : bool separator_seen=false;
80 :
81 10128 : if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
82 : {
83 4272 : sNumberString.append(rStr[io_rPos]);
84 4272 : aChar = rStr[++io_rPos];
85 : }
86 :
87 55292 : while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
88 10144 : || (!separator_seen && sal_Unicode('.') == aChar))
89 : {
90 24892 : if (sal_Unicode('.') == aChar) separator_seen = true;
91 24892 : sNumberString.append(rStr[io_rPos]);
92 24892 : aChar = rStr[++io_rPos];
93 : }
94 :
95 10128 : 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 10128 : 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 10128 : NULL );
121 10128 : return ( eStatus == rtl_math_ConversionStatus_Ok );
122 : }
123 :
124 0 : return false;
125 : }
126 :
127 10128 : bool lcl_importDoubleAndSpaces( double& o_fRetval,
128 : sal_Int32& io_rPos,
129 : const OUString& rStr,
130 : const sal_Int32 nLen )
131 : {
132 10128 : if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr) )
133 0 : return false;
134 :
135 10128 : lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
136 :
137 10128 : 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 5654 : void lcl_putNumberChar( OUStringBuffer& rStr,
166 : double fValue )
167 : {
168 5654 : rStr.append( fValue );
169 5654 : }
170 :
171 5654 : void lcl_putNumberCharWithSpace( OUStringBuffer& rStr,
172 : double fValue,
173 : double fOldValue,
174 : bool bUseRelativeCoordinates )
175 : {
176 5654 : if( bUseRelativeCoordinates )
177 5644 : fValue -= fOldValue;
178 :
179 5654 : const sal_Int32 aLen( rStr.getLength() );
180 5654 : if(aLen > 0)
181 : {
182 5654 : if( lcl_isOnNumberChar(rStr[aLen - 1], false) &&
183 : fValue >= 0.0 )
184 : {
185 1888 : rStr.append( sal_Unicode(' ') );
186 : }
187 : }
188 :
189 5654 : lcl_putNumberChar(rStr, fValue);
190 5654 : }
191 :
192 4022 : inline sal_Unicode lcl_getCommand( sal_Char cUpperCaseCommand,
193 : sal_Char cLowerCaseCommand,
194 : bool bUseRelativeCoordinates )
195 : {
196 4022 : return bUseRelativeCoordinates ? cLowerCaseCommand : cUpperCaseCommand;
197 : }
198 : }
199 :
200 208 : bool importFromSvgD(B2DPolyPolygon& o_rPolyPolygon, const OUString& rSvgDStatement, bool bWrongPositionAfterZ)
201 : {
202 208 : o_rPolyPolygon.clear();
203 208 : const sal_Int32 nLen(rSvgDStatement.getLength());
204 208 : sal_Int32 nPos(0);
205 208 : bool bIsClosed(false);
206 208 : double nLastX( 0.0 );
207 208 : double nLastY( 0.0 );
208 208 : B2DPolygon aCurrPoly;
209 :
210 : // skip initial whitespace
211 208 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
212 :
213 6122 : while(nPos < nLen)
214 : {
215 5706 : bool bRelative(false);
216 5706 : bool bMoveTo(false);
217 5706 : const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
218 :
219 5706 : switch(aCurrChar)
220 : {
221 : case 'z' :
222 : case 'Z' :
223 : {
224 926 : nPos++;
225 926 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
226 :
227 : // remember closed state of current polygon
228 926 : bIsClosed = true;
229 :
230 : // update current point - we're back at the start
231 926 : if( aCurrPoly.count() && !bWrongPositionAfterZ)
232 : {
233 920 : const B2DPoint aFirst( aCurrPoly.getB2DPoint(0) );
234 920 : nLastX = aFirst.getX();
235 920 : nLastY = aFirst.getY();
236 : }
237 926 : break;
238 : }
239 :
240 : case 'm' :
241 : case 'M' :
242 : {
243 956 : bMoveTo = true;
244 : // FALLTHROUGH intended
245 : }
246 : case 'l' :
247 : case 'L' :
248 : {
249 1044 : if('m' == aCurrChar || 'l' == aCurrChar)
250 : {
251 962 : bRelative = true;
252 : }
253 :
254 1044 : if(bMoveTo)
255 : {
256 : // new polygon start, finish old one
257 956 : if(aCurrPoly.count())
258 : {
259 : // add current polygon
260 748 : if(bIsClosed)
261 : {
262 728 : closeWithGeometryChange(aCurrPoly);
263 : }
264 :
265 748 : o_rPolyPolygon.append(aCurrPoly);
266 :
267 : // reset import values
268 748 : bIsClosed = false;
269 748 : aCurrPoly.clear();
270 : }
271 : }
272 :
273 1044 : nPos++;
274 1044 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
275 :
276 1044 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
277 : {
278 : double nX, nY;
279 :
280 2018 : if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
281 2018 : if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
282 :
283 2018 : if(bRelative)
284 : {
285 1930 : nX += nLastX;
286 1930 : nY += nLastY;
287 : }
288 :
289 : // set last position
290 2018 : nLastX = nX;
291 2018 : nLastY = nY;
292 :
293 : // add point
294 2018 : aCurrPoly.append(B2DPoint(nX, nY));
295 : }
296 1044 : break;
297 : }
298 :
299 : case 'h' :
300 : {
301 1808 : bRelative = true;
302 : // FALLTHROUGH intended
303 : }
304 : case 'H' :
305 : {
306 1808 : nPos++;
307 1808 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
308 :
309 5514 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
310 : {
311 1898 : double nX, nY(nLastY);
312 :
313 1898 : if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
314 :
315 1898 : if(bRelative)
316 : {
317 1898 : nX += nLastX;
318 : }
319 :
320 : // set last position
321 1898 : nLastX = nX;
322 :
323 : // add point
324 1898 : aCurrPoly.append(B2DPoint(nX, nY));
325 : }
326 1808 : break;
327 : }
328 :
329 : case 'v' :
330 : {
331 1798 : bRelative = true;
332 : // FALLTHROUGH intended
333 : }
334 : case 'V' :
335 : {
336 1798 : nPos++;
337 1798 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
338 :
339 5466 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
340 : {
341 1870 : double nX(nLastX), nY;
342 :
343 1870 : if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
344 :
345 1870 : if(bRelative)
346 : {
347 1870 : nY += nLastY;
348 : }
349 :
350 : // set last position
351 1870 : nLastY = nY;
352 :
353 : // add point
354 1870 : aCurrPoly.append(B2DPoint(nX, nY));
355 : }
356 1798 : break;
357 : }
358 :
359 : case 's' :
360 : {
361 36 : bRelative = true;
362 : // FALLTHROUGH intended
363 : }
364 : case 'S' :
365 : {
366 36 : nPos++;
367 36 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
368 :
369 36 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
370 : {
371 : double nX, nY;
372 : double nX2, nY2;
373 :
374 54 : if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
375 54 : if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
376 54 : if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
377 54 : if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
378 :
379 54 : if(bRelative)
380 : {
381 54 : nX2 += nLastX;
382 54 : nY2 += nLastY;
383 54 : nX += nLastX;
384 54 : nY += nLastY;
385 : }
386 :
387 : // ensure existance of start point
388 54 : 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 54 : B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
396 54 : const sal_uInt32 nIndex(aCurrPoly.count() - 1);
397 :
398 54 : if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
399 : {
400 54 : const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
401 54 : const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
402 :
403 : // use mirrored previous control point
404 54 : aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
405 54 : aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
406 : }
407 :
408 : // append curved edge
409 54 : aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
410 :
411 : // set last position
412 54 : nLastX = nX;
413 54 : nLastY = nY;
414 54 : }
415 36 : break;
416 : }
417 :
418 : case 'c' :
419 : {
420 74 : bRelative = true;
421 : // FALLTHROUGH intended
422 : }
423 : case 'C' :
424 : {
425 74 : nPos++;
426 74 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
427 :
428 74 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
429 : {
430 : double nX, nY;
431 : double nX1, nY1;
432 : double nX2, nY2;
433 :
434 338 : if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
435 338 : if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
436 338 : if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
437 338 : if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
438 338 : if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
439 338 : if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
440 :
441 338 : if(bRelative)
442 : {
443 338 : nX1 += nLastX;
444 338 : nY1 += nLastY;
445 338 : nX2 += nLastX;
446 338 : nY2 += nLastY;
447 338 : nX += nLastX;
448 338 : nY += nLastY;
449 : }
450 :
451 : // ensure existance of start point
452 338 : if(!aCurrPoly.count())
453 : {
454 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
455 : }
456 :
457 : // append curved edge
458 338 : aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
459 :
460 : // set last position
461 338 : nLastX = nX;
462 338 : nLastY = nY;
463 : }
464 74 : break;
465 : }
466 :
467 : // #100617# quadratic beziers are imported as cubic ones
468 : case 'q' :
469 : {
470 20 : bRelative = true;
471 : // FALLTHROUGH intended
472 : }
473 : case 'Q' :
474 : {
475 20 : nPos++;
476 20 : lcl_skipSpaces(nPos, rSvgDStatement, nLen);
477 :
478 20 : while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
479 : {
480 : double nX, nY;
481 : double nX1, nY1;
482 :
483 20 : if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
484 20 : if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
485 20 : if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
486 20 : if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
487 :
488 20 : if(bRelative)
489 : {
490 20 : nX1 += nLastX;
491 20 : nY1 += nLastY;
492 20 : nX += nLastX;
493 20 : nY += nLastY;
494 : }
495 :
496 : // calculate the cubic bezier coefficients from the quadratic ones
497 20 : const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
498 20 : const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
499 20 : const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
500 20 : const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
501 :
502 : // ensure existance of start point
503 20 : if(!aCurrPoly.count())
504 : {
505 0 : aCurrPoly.append(B2DPoint(nLastX, nLastY));
506 : }
507 :
508 : // append curved edge
509 20 : aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
510 :
511 : // set last position
512 20 : nLastX = nX;
513 20 : nLastY = nY;
514 : }
515 20 : 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 208 : if(aCurrPoly.count())
769 : {
770 208 : const B2DPoint aFirstPoint(aCurrPoly.getB2DPoint(0));
771 208 : const B2DPoint aLastPoint(aCurrPoly.getB2DPoint(aCurrPoly.count()-1));
772 624 : if ( (aFirstPoint.getX()-aLastPoint.getX())*(aFirstPoint.getX()-aLastPoint.getX()) +
773 430 : (aFirstPoint.getY()-aLastPoint.getY())*(aFirstPoint.getY()-aLastPoint.getY()) < 1 ) bIsClosed = true;
774 : // end-process last poly
775 208 : if(bIsClosed)
776 : {
777 198 : closeWithGeometryChange(aCurrPoly);
778 : }
779 :
780 208 : o_rPolyPolygon.append(aCurrPoly);
781 : }
782 :
783 208 : 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 136 : OUString exportToSvgD(
813 : const B2DPolyPolygon& rPolyPolygon,
814 : bool bUseRelativeCoordinates,
815 : bool bDetectQuadraticBeziers)
816 : {
817 136 : const sal_uInt32 nCount(rPolyPolygon.count());
818 136 : OUStringBuffer aResult;
819 136 : B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
820 :
821 538 : for(sal_uInt32 i(0); i < nCount; i++)
822 : {
823 402 : const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
824 402 : const sal_uInt32 nPointCount(aPolygon.count());
825 :
826 402 : if(nPointCount)
827 : {
828 402 : const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
829 402 : const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
830 402 : sal_Unicode aLastSVGCommand(' '); // last SVG command char
831 402 : B2DPoint aLeft, aRight; // for quadratic bezier test
832 :
833 : // handle polygon start point
834 402 : B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
835 402 : aResult.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates));
836 402 : lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
837 402 : lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
838 402 : aLastSVGCommand = lcl_getCommand('L', 'l', bUseRelativeCoordinates);
839 402 : aCurrentSVGPosition = aEdgeStart;
840 :
841 3676 : for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
842 : {
843 : // prepare access to next point
844 3274 : const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
845 3274 : const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
846 :
847 : // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
848 : const bool bEdgeIsBezier(bPolyUsesControlPoints
849 3274 : && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
850 :
851 3274 : if(bEdgeIsBezier)
852 : {
853 : // handle bezier edge
854 218 : const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
855 218 : const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
856 218 : 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 218 : && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
872 :
873 218 : 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 218 : aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
883 218 : aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
884 218 : bIsQuadraticBezier = aLeft.equal(aRight);
885 : }
886 :
887 218 : if(bIsQuadraticBezier)
888 : {
889 : // approximately equal, export as quadratic bezier
890 20 : 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 20 : const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
908 :
909 20 : if(aLastSVGCommand != aCommand)
910 : {
911 20 : aResult.append(aCommand);
912 20 : aLastSVGCommand = aCommand;
913 : }
914 :
915 20 : lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
916 20 : lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
917 20 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
918 20 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
919 20 : aLastSVGCommand = aCommand;
920 20 : aCurrentSVGPosition = aEdgeEnd;
921 : }
922 : }
923 : else
924 : {
925 : // export as cubic bezier
926 198 : if(bSymmetricAtEdgeStart)
927 : {
928 42 : const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
929 :
930 42 : if(aLastSVGCommand != aCommand)
931 : {
932 34 : aResult.append(aCommand);
933 34 : aLastSVGCommand = aCommand;
934 : }
935 :
936 42 : lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
937 42 : lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
938 42 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
939 42 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
940 42 : aLastSVGCommand = aCommand;
941 42 : aCurrentSVGPosition = aEdgeEnd;
942 : }
943 : else
944 : {
945 156 : const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
946 :
947 156 : if(aLastSVGCommand != aCommand)
948 : {
949 62 : aResult.append(aCommand);
950 62 : aLastSVGCommand = aCommand;
951 : }
952 :
953 156 : lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
954 156 : lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
955 156 : lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
956 156 : lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
957 156 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
958 156 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
959 156 : aLastSVGCommand = aCommand;
960 156 : aCurrentSVGPosition = aEdgeEnd;
961 : }
962 218 : }
963 : }
964 : else
965 : {
966 : // straight edge
967 3056 : 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 2758 : const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
975 2758 : const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
976 :
977 2758 : if(bXEqual && bYEqual)
978 : {
979 : // point is a double point; do not export at all
980 : }
981 2694 : else if(bXEqual)
982 : {
983 : // export as vertical line
984 902 : const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
985 :
986 902 : if(aLastSVGCommand != aCommand)
987 : {
988 860 : aResult.append(aCommand);
989 860 : aLastSVGCommand = aCommand;
990 : }
991 :
992 902 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
993 902 : aCurrentSVGPosition = aEdgeEnd;
994 : }
995 1792 : else if(bYEqual)
996 : {
997 : // export as horizontal line
998 820 : const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
999 :
1000 820 : if(aLastSVGCommand != aCommand)
1001 : {
1002 760 : aResult.append(aCommand);
1003 760 : aLastSVGCommand = aCommand;
1004 : }
1005 :
1006 820 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1007 820 : aCurrentSVGPosition = aEdgeEnd;
1008 : }
1009 : else
1010 : {
1011 : // export as line
1012 972 : const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
1013 :
1014 972 : if(aLastSVGCommand != aCommand)
1015 : {
1016 12 : aResult.append(aCommand);
1017 12 : aLastSVGCommand = aCommand;
1018 : }
1019 :
1020 972 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
1021 972 : lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
1022 972 : aCurrentSVGPosition = aEdgeEnd;
1023 : }
1024 : }
1025 : }
1026 :
1027 : // prepare edge start for next loop step
1028 3274 : aEdgeStart = aEdgeEnd;
1029 3274 : }
1030 :
1031 : // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
1032 402 : if(aPolygon.isClosed())
1033 : {
1034 306 : aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
1035 : // return to first point
1036 306 : aCurrentSVGPosition = aPolygon.getB2DPoint(0);
1037 402 : }
1038 : }
1039 402 : }
1040 :
1041 136 : return aResult.makeStringAndClear();
1042 : }
1043 : }
1044 : }
1045 :
1046 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|