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