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 // #123465# no need to do the old closeWithGeometryChange 324 // corerection on SVG polygons; this even may lead to wrong 325 // results e.g. for marker processing 326 aCurrPoly.setClosed(true); 327 } 328 329 o_rPolyPolygon.append(aCurrPoly); 330 331 // reset import values 332 bIsClosed = false; 333 aCurrPoly.clear(); 334 } 335 } 336 337 nPos++; 338 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 339 340 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 341 { 342 double nX, nY; 343 344 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 345 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 346 347 if(bRelative) 348 { 349 nX += nLastX; 350 nY += nLastY; 351 } 352 353 // set last position 354 nLastX = nX; 355 nLastY = nY; 356 357 // add point 358 aCurrPoly.append(B2DPoint(nX, nY)); 359 } 360 break; 361 } 362 363 case 'h' : 364 { 365 bRelative = true; 366 // FALLTHROUGH intended 367 } 368 case 'H' : 369 { 370 nPos++; 371 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 372 373 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 374 { 375 double nX, nY(nLastY); 376 377 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 378 379 if(bRelative) 380 { 381 nX += nLastX; 382 } 383 384 // set last position 385 nLastX = nX; 386 387 // add point 388 aCurrPoly.append(B2DPoint(nX, nY)); 389 } 390 break; 391 } 392 393 case 'v' : 394 { 395 bRelative = true; 396 // FALLTHROUGH intended 397 } 398 case 'V' : 399 { 400 nPos++; 401 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 402 403 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 404 { 405 double nX(nLastX), nY; 406 407 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 408 409 if(bRelative) 410 { 411 nY += nLastY; 412 } 413 414 // set last position 415 nLastY = nY; 416 417 // add point 418 aCurrPoly.append(B2DPoint(nX, nY)); 419 } 420 break; 421 } 422 423 case 's' : 424 { 425 bRelative = true; 426 // FALLTHROUGH intended 427 } 428 case 'S' : 429 { 430 nPos++; 431 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 432 433 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 434 { 435 double nX, nY; 436 double nX2, nY2; 437 438 if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 439 if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 440 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 441 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 442 443 if(bRelative) 444 { 445 nX2 += nLastX; 446 nY2 += nLastY; 447 nX += nLastX; 448 nY += nLastY; 449 } 450 451 // ensure existance of start point 452 if(!aCurrPoly.count()) 453 { 454 aCurrPoly.append(B2DPoint(nLastX, nLastY)); 455 } 456 457 // get first control point. It's the reflection of the PrevControlPoint 458 // of the last point. If not existent, use current point (see SVG) 459 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 460 const sal_uInt32 nIndex(aCurrPoly.count() - 1); 461 462 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 463 { 464 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 465 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 466 467 // use mirrored previous control point 468 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 469 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 470 } 471 472 // append curved edge 473 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY)); 474 475 // set last position 476 nLastX = nX; 477 nLastY = nY; 478 } 479 break; 480 } 481 482 case 'c' : 483 { 484 bRelative = true; 485 // FALLTHROUGH intended 486 } 487 case 'C' : 488 { 489 nPos++; 490 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 491 492 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 493 { 494 double nX, nY; 495 double nX1, nY1; 496 double nX2, nY2; 497 498 if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 499 if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 500 if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; 501 if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; 502 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 503 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 504 505 if(bRelative) 506 { 507 nX1 += nLastX; 508 nY1 += nLastY; 509 nX2 += nLastX; 510 nY2 += nLastY; 511 nX += nLastX; 512 nY += nLastY; 513 } 514 515 // ensure existance of start point 516 if(!aCurrPoly.count()) 517 { 518 aCurrPoly.append(B2DPoint(nLastX, nLastY)); 519 } 520 521 // append curved edge 522 aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY)); 523 524 // set last position 525 nLastX = nX; 526 nLastY = nY; 527 } 528 break; 529 } 530 531 // #100617# quadratic beziers are imported as cubic ones 532 case 'q' : 533 { 534 bRelative = true; 535 // FALLTHROUGH intended 536 } 537 case 'Q' : 538 { 539 nPos++; 540 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 541 542 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 543 { 544 double nX, nY; 545 double nX1, nY1; 546 547 if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; 548 if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; 549 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 550 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 551 552 if(bRelative) 553 { 554 nX1 += nLastX; 555 nY1 += nLastY; 556 nX += nLastX; 557 nY += nLastY; 558 } 559 560 // calculate the cubic bezier coefficients from the quadratic ones 561 const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0); 562 const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0); 563 const double nX2Prime((nX1 * 2.0 + nX) / 3.0); 564 const double nY2Prime((nY1 * 2.0 + nY) / 3.0); 565 566 // ensure existance of start point 567 if(!aCurrPoly.count()) 568 { 569 aCurrPoly.append(B2DPoint(nLastX, nLastY)); 570 } 571 572 // append curved edge 573 aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); 574 575 // set last position 576 nLastX = nX; 577 nLastY = nY; 578 } 579 break; 580 } 581 582 // #100617# relative quadratic beziers are imported as cubic 583 case 't' : 584 { 585 bRelative = true; 586 // FALLTHROUGH intended 587 } 588 case 'T' : 589 { 590 nPos++; 591 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 592 593 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 594 { 595 double nX, nY; 596 597 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 598 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 599 600 if(bRelative) 601 { 602 nX += nLastX; 603 nY += nLastY; 604 } 605 606 // ensure existance of start point 607 if(!aCurrPoly.count()) 608 { 609 aCurrPoly.append(B2DPoint(nLastX, nLastY)); 610 } 611 612 // get first control point. It's the reflection of the PrevControlPoint 613 // of the last point. If not existent, use current point (see SVG) 614 B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); 615 const sal_uInt32 nIndex(aCurrPoly.count() - 1); 616 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); 617 618 if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) 619 { 620 const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); 621 622 // use mirrored previous control point 623 aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); 624 aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); 625 } 626 627 if(!aPrevControl.equal(aPrevPoint)) 628 { 629 // there is a prev control point, and we have the already mirrored one 630 // in aPrevControl. We also need the quadratic control point for this 631 // new quadratic segment to calculate the 2nd cubic control point 632 const B2DPoint aQuadControlPoint( 633 ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0, 634 ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0); 635 636 // calculate the cubic bezier coefficients from the quadratic ones. 637 const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0); 638 const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0); 639 640 // append curved edge, use mirrored cubic control point directly 641 aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); 642 } 643 else 644 { 645 // when no previous control, SVG says to use current point -> straight line. 646 // Just add end point 647 aCurrPoly.append(B2DPoint(nX, nY)); 648 } 649 650 // set last position 651 nLastX = nX; 652 nLastY = nY; 653 } 654 break; 655 } 656 657 case 'a' : 658 { 659 bRelative = true; 660 // FALLTHROUGH intended 661 } 662 case 'A' : 663 { 664 nPos++; 665 lcl_skipSpaces(nPos, rSvgDStatement, nLen); 666 667 while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) 668 { 669 double nX, nY; 670 double fRX, fRY, fPhi; 671 sal_Int32 bLargeArcFlag, bSweepFlag; 672 673 if(!lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false; 674 if(!lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false; 675 if(!lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false; 676 if(!lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false; 677 if(!lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false; 678 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; 679 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; 680 681 if(bRelative) 682 { 683 nX += nLastX; 684 nY += nLastY; 685 } 686 687 const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1)); 688 689 if( nX == nLastX && nY == nLastY ) 690 continue; // start==end -> skip according to SVG spec 691 692 if( fRX == 0.0 || fRY == 0.0 ) 693 { 694 // straight line segment according to SVG spec 695 aCurrPoly.append(B2DPoint(nX, nY)); 696 } 697 else 698 { 699 // normalize according to SVG spec 700 fRX=fabs(fRX); fRY=fabs(fRY); 701 702 // from the SVG spec, appendix F.6.4 703 704 // |x1'| |cos phi sin phi| |(x1 - x2)/2| 705 // |y1'| = |-sin phi cos phi| |(y1 - y2)/2| 706 const B2DPoint p1(nLastX, nLastY); 707 const B2DPoint p2(nX, nY); 708 B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180)); 709 710 const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) ); 711 712 // ______________________________________ rx y1' 713 // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry 714 // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1' 715 // rx 716 // chose + if f_A != f_S 717 // chose - if f_A = f_S 718 B2DPoint aCenter_prime; 719 const double fRadicant( 720 (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/ 721 (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX())); 722 if( fRadicant < 0.0 ) 723 { 724 // no solution - according to SVG 725 // spec, scale up ellipse 726 // uniformly such that it passes 727 // through end points (denominator 728 // of radicant solved for fRY, 729 // with s=fRX/fRY) 730 const double fRatio(fRX/fRY); 731 const double fRadicant2( 732 p1_prime.getY()*p1_prime.getY() + 733 p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio)); 734 if( fRadicant2 < 0.0 ) 735 { 736 // only trivial solution, one 737 // of the axes 0 -> straight 738 // line segment according to 739 // SVG spec 740 aCurrPoly.append(B2DPoint(nX, nY)); 741 continue; 742 } 743 744 fRY=sqrt(fRadicant2); 745 fRX=fRatio*fRY; 746 747 // keep center_prime forced to (0,0) 748 } 749 else 750 { 751 const double fFactor( 752 (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) * 753 sqrt(fRadicant)); 754 755 // actually calculate center_prime 756 aCenter_prime = B2DPoint( 757 fFactor*fRX*p1_prime.getY()/fRY, 758 -fFactor*fRY*p1_prime.getX()/fRX); 759 } 760 761 // + u - v 762 // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx)) 763 // - ||u|| ||v|| 764 765 // 1 | (x1' - cx')/rx | 766 // theta1 = angle(( ), | | ) 767 // 0 | (y1' - cy')/ry | 768 const B2DPoint aRadii(fRX,fRY); 769 double fTheta1( 770 B2DVector(1.0,0.0).angle( 771 (p1_prime-aCenter_prime)/aRadii)); 772 773 // |1| | (-x1' - cx')/rx | 774 // theta2 = angle( | | , | | ) 775 // |0| | (-y1' - cy')/ry | 776 double fTheta2( 777 B2DVector(1.0,0.0).angle( 778 (-p1_prime-aCenter_prime)/aRadii)); 779 780 // map both angles to [0,2pi) 781 fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI); 782 fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI); 783 784 // make sure the large arc is taken 785 // (since 786 // createPolygonFromEllipseSegment() 787 // normalizes to e.g. cw arc) 788 789 // ALG: In my opinion flipping the segment only 790 // depends on the sweep flag. At least, this gives 791 // correct results forthe SVG example (see SVG doc 8.3.8 ff) 792 // 793 //const bool bFlipSegment( (bLargeArcFlag!=0) == 794 // (fmod(fTheta2+2*M_PI-fTheta1, 795 // 2*M_PI)<M_PI) ); 796 const bool bFlipSegment(!bSweepFlag); 797 798 if( bFlipSegment ) 799 std::swap(fTheta1,fTheta2); 800 801 // finally, create bezier polygon from this 802 B2DPolygon aSegment( 803 tools::createPolygonFromUnitEllipseSegment( 804 fTheta1, fTheta2 )); 805 806 // transform ellipse by rotation & move to final center 807 aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY); 808 aTransform.translate(aCenter_prime.getX(), 809 aCenter_prime.getY()); 810 aTransform.rotate(fPhi*M_PI/180); 811 const B2DPoint aOffset((p1+p2)/2.0); 812 aTransform.translate(aOffset.getX(), 813 aOffset.getY()); 814 aSegment.transform(aTransform); 815 816 // createPolygonFromEllipseSegment() 817 // always creates arcs that are 818 // positively oriented - flip polygon 819 // if we swapped angles above 820 if( bFlipSegment ) 821 aSegment.flip(); 822 aCurrPoly.append(aSegment); 823 } 824 825 // set last position 826 nLastX = nX; 827 nLastY = nY; 828 } 829 break; 830 } 831 832 default: 833 { 834 OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!"); 835 OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar); 836 ++nPos; 837 break; 838 } 839 } 840 } 841 842 if(aCurrPoly.count()) 843 { 844 // end-process last poly 845 if(bIsClosed) 846 { 847 // #123465# no need to do the old closeWithGeometryChange 848 // corerection on SVG polygons; this even may lead to wrong 849 // results e.g. for marker processing 850 aCurrPoly.setClosed(true); 851 } 852 853 o_rPolyPolygon.append(aCurrPoly); 854 } 855 856 return true; 857 } 858 859 bool importFromSvgPoints( B2DPolygon& o_rPoly, 860 const ::rtl::OUString& rSvgPointsAttribute ) 861 { 862 o_rPoly.clear(); 863 const sal_Int32 nLen(rSvgPointsAttribute.getLength()); 864 sal_Int32 nPos(0); 865 double nX, nY; 866 867 // skip initial whitespace 868 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 869 870 while(nPos < nLen) 871 { 872 if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false; 873 if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false; 874 875 // add point 876 o_rPoly.append(B2DPoint(nX, nY)); 877 878 // skip to next number, or finish 879 lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); 880 } 881 882 return true; 883 } 884 885 ::rtl::OUString exportToSvgD( 886 const B2DPolyPolygon& rPolyPolygon, 887 bool bUseRelativeCoordinates, 888 bool bDetectQuadraticBeziers) 889 { 890 const sal_uInt32 nCount(rPolyPolygon.count()); 891 ::rtl::OUStringBuffer aResult; 892 B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point 893 894 for(sal_uInt32 i(0); i < nCount; i++) 895 { 896 const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i)); 897 const sal_uInt32 nPointCount(aPolygon.count()); 898 899 if(nPointCount) 900 { 901 const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed()); 902 const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1); 903 sal_Unicode aLastSVGCommand(' '); // last SVG command char 904 B2DPoint aLeft, aRight; // for quadratic bezier test 905 906 // handle polygon start point 907 B2DPoint aEdgeStart(aPolygon.getB2DPoint(0)); 908 aResult.append(lcl_getCommand('M', 'm', bUseRelativeCoordinates)); 909 lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 910 lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 911 aLastSVGCommand = lcl_getCommand('L', 'l', bUseRelativeCoordinates); 912 aCurrentSVGPosition = aEdgeStart; 913 914 for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++) 915 { 916 // prepare access to next point 917 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); 918 const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex)); 919 920 // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex) 921 const bool bEdgeIsBezier(bPolyUsesControlPoints 922 && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex))); 923 924 if(bEdgeIsBezier) 925 { 926 // handle bezier edge 927 const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex)); 928 const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex)); 929 bool bIsQuadraticBezier(false); 930 931 // check continuity at current edge's start point. For SVG, do NOT use an 932 // existing continuity since no 'S' or 's' statement should be written. At 933 // import, that 'previous' control vector is not available. SVG documentation 934 // says for interpretation: 935 // 936 // "(If there is no previous command or if the previous command was 937 // not an C, c, S or s, assume the first control point is coincident 938 // with the current point.)" 939 // 940 // That's what is done from our import, so avoid exporting it as first statement 941 // is necessary. 942 const bool bSymmetricAtEdgeStart( 943 0 != nIndex 944 && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex)); 945 946 if(bDetectQuadraticBeziers) 947 { 948 // check for quadratic beziers - that's 949 // the case if both control points are in 950 // the same place when they are prolonged 951 // to the common quadratic control point 952 // 953 // Left: P = (3P1 - P0) / 2 954 // Right: P = (3P2 - P3) / 2 955 aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0); 956 aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0); 957 bIsQuadraticBezier = aLeft.equal(aRight); 958 } 959 960 if(bIsQuadraticBezier) 961 { 962 // approximately equal, export as quadratic bezier 963 if(bSymmetricAtEdgeStart) 964 { 965 const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates)); 966 967 if(aLastSVGCommand != aCommand) 968 { 969 aResult.append(aCommand); 970 aLastSVGCommand = aCommand; 971 } 972 973 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 974 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 975 aLastSVGCommand = aCommand; 976 aCurrentSVGPosition = aEdgeEnd; 977 } 978 else 979 { 980 const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates)); 981 982 if(aLastSVGCommand != aCommand) 983 { 984 aResult.append(aCommand); 985 aLastSVGCommand = aCommand; 986 } 987 988 lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 989 lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 990 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 991 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 992 aLastSVGCommand = aCommand; 993 aCurrentSVGPosition = aEdgeEnd; 994 } 995 } 996 else 997 { 998 // export as cubic bezier 999 if(bSymmetricAtEdgeStart) 1000 { 1001 const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates)); 1002 1003 if(aLastSVGCommand != aCommand) 1004 { 1005 aResult.append(aCommand); 1006 aLastSVGCommand = aCommand; 1007 } 1008 1009 lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1010 lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1011 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1012 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1013 aLastSVGCommand = aCommand; 1014 aCurrentSVGPosition = aEdgeEnd; 1015 } 1016 else 1017 { 1018 const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates)); 1019 1020 if(aLastSVGCommand != aCommand) 1021 { 1022 aResult.append(aCommand); 1023 aLastSVGCommand = aCommand; 1024 } 1025 1026 lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1027 lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1028 lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1029 lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1030 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1031 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1032 aLastSVGCommand = aCommand; 1033 aCurrentSVGPosition = aEdgeEnd; 1034 } 1035 } 1036 } 1037 else 1038 { 1039 // straight edge 1040 if(0 == nNextIndex) 1041 { 1042 // it's a closed polygon's last edge and it's not a bezier edge, so there is 1043 // no need to write it 1044 } 1045 else 1046 { 1047 const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX()); 1048 const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY()); 1049 1050 if(bXEqual && bYEqual) 1051 { 1052 // point is a double point; do not export at all 1053 } 1054 else if(bXEqual) 1055 { 1056 // export as vertical line 1057 const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates)); 1058 1059 if(aLastSVGCommand != aCommand) 1060 { 1061 aResult.append(aCommand); 1062 aLastSVGCommand = aCommand; 1063 } 1064 1065 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1066 aCurrentSVGPosition = aEdgeEnd; 1067 } 1068 else if(bYEqual) 1069 { 1070 // export as horizontal line 1071 const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates)); 1072 1073 if(aLastSVGCommand != aCommand) 1074 { 1075 aResult.append(aCommand); 1076 aLastSVGCommand = aCommand; 1077 } 1078 1079 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1080 aCurrentSVGPosition = aEdgeEnd; 1081 } 1082 else 1083 { 1084 // export as line 1085 const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates)); 1086 1087 if(aLastSVGCommand != aCommand) 1088 { 1089 aResult.append(aCommand); 1090 aLastSVGCommand = aCommand; 1091 } 1092 1093 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); 1094 lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); 1095 aCurrentSVGPosition = aEdgeEnd; 1096 } 1097 } 1098 } 1099 1100 // prepare edge start for next loop step 1101 aEdgeStart = aEdgeEnd; 1102 } 1103 1104 // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched) 1105 if(aPolygon.isClosed()) 1106 { 1107 aResult.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates)); 1108 } 1109 } 1110 } 1111 1112 return aResult.makeStringAndClear(); 1113 } 1114 } 1115 } 1116 1117 // eof 1118