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