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