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 // MARKER(update_precomp.py): autogen include statement, do not remove 23 #include "precompiled_svgio.hxx" 24 25 #include <svgio/svgreader/svgtextpathnode.hxx> 26 #include <svgio/svgreader/svgstyleattributes.hxx> 27 #include <svgio/svgreader/svgpathnode.hxx> 28 #include <svgio/svgreader/svgdocument.hxx> 29 #include <svgio/svgreader/svgtrefnode.hxx> 30 #include <basegfx/polygon/b2dpolygon.hxx> 31 #include <basegfx/polygon/b2dpolygontools.hxx> 32 #include <drawinglayer/primitive2d/textbreakuphelper.hxx> 33 #include <drawinglayer/primitive2d/groupprimitive2d.hxx> 34 #include <basegfx/curve/b2dcubicbezier.hxx> 35 #include <basegfx/curve/b2dbeziertools.hxx> 36 37 ////////////////////////////////////////////////////////////////////////////// 38 39 namespace svgio 40 { 41 namespace svgreader 42 { 43 class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper 44 { 45 private: 46 const basegfx::B2DPolygon& mrPolygon; 47 const double mfBasegfxPathLength; 48 const double mfUserToBasegfx; 49 double mfPosition; 50 const basegfx::B2DPoint& mrTextStart; 51 52 const sal_uInt32 mnMaxIndex; 53 sal_uInt32 mnIndex; 54 basegfx::B2DCubicBezier maCurrentSegment; 55 basegfx::B2DCubicBezierHelper* mpB2DCubicBezierHelper; 56 double mfCurrentSegmentLength; 57 double mfSegmentStartPosition; 58 59 protected: 60 /// allow user callback to allow changes to the new TextTransformation. Default 61 /// does nothing. 62 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength); 63 64 void freeB2DCubicBezierHelper(); 65 basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper(); 66 void advanceToPosition(double fNewPosition); 67 68 public: 69 pathTextBreakupHelper( 70 const drawinglayer::primitive2d::Primitive2DReference& rxSource, 71 const basegfx::B2DPolygon& rPolygon, 72 const double fBasegfxPathLength, 73 const double fUserToBasegfx, 74 double fPosition, 75 const basegfx::B2DPoint& rTextStart); 76 virtual ~pathTextBreakupHelper(); 77 78 // read access to evtl. advanced position 79 double getPosition() const { return mfPosition; } 80 81 // get length of given text 82 double getLength(const rtl::OUString& rText) const; 83 }; 84 85 double pathTextBreakupHelper::getLength(const rtl::OUString& rText) const 86 { 87 const sal_uInt32 nLength(rText.getLength()); 88 89 if(nLength) 90 { 91 return getTextLayouter().getTextWidth(rText, 0, nLength); 92 } 93 94 return 0.0; 95 } 96 97 void pathTextBreakupHelper::freeB2DCubicBezierHelper() 98 { 99 if(mpB2DCubicBezierHelper) 100 { 101 delete mpB2DCubicBezierHelper; 102 mpB2DCubicBezierHelper = 0; 103 } 104 } 105 106 basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper() 107 { 108 if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier()) 109 { 110 mpB2DCubicBezierHelper = new basegfx::B2DCubicBezierHelper(maCurrentSegment); 111 } 112 113 return mpB2DCubicBezierHelper; 114 } 115 116 void pathTextBreakupHelper::advanceToPosition(double fNewPosition) 117 { 118 while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex) 119 { 120 mfSegmentStartPosition += mfCurrentSegmentLength; 121 mnIndex++; 122 123 if(mnIndex < mnMaxIndex) 124 { 125 freeB2DCubicBezierHelper(); 126 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment); 127 maCurrentSegment.testAndSolveTrivialBezier(); 128 mfCurrentSegmentLength = getB2DCubicBezierHelper() 129 ? getB2DCubicBezierHelper()->getLength() 130 : maCurrentSegment.getLength(); 131 } 132 } 133 134 mfPosition = fNewPosition; 135 } 136 137 pathTextBreakupHelper::pathTextBreakupHelper( 138 const drawinglayer::primitive2d::Primitive2DReference& rxSource, 139 const basegfx::B2DPolygon& rPolygon, 140 const double fBasegfxPathLength, 141 const double fUserToBasegfx, 142 double fPosition, 143 const basegfx::B2DPoint& rTextStart) 144 : drawinglayer::primitive2d::TextBreakupHelper(rxSource), 145 mrPolygon(rPolygon), 146 mfBasegfxPathLength(fBasegfxPathLength), 147 mfUserToBasegfx(fUserToBasegfx), 148 mfPosition(0.0), 149 mrTextStart(rTextStart), 150 mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1), 151 mnIndex(0), 152 maCurrentSegment(), 153 mpB2DCubicBezierHelper(0), 154 mfCurrentSegmentLength(0.0), 155 mfSegmentStartPosition(0.0) 156 { 157 mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment); 158 mfCurrentSegmentLength = maCurrentSegment.getLength(); 159 160 advanceToPosition(fPosition); 161 } 162 163 pathTextBreakupHelper::~pathTextBreakupHelper() 164 { 165 freeB2DCubicBezierHelper(); 166 } 167 168 bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) 169 { 170 bool bRetval(false); 171 172 if(mfPosition < mfBasegfxPathLength && nLength && getCastedSource() && mnIndex < mnMaxIndex) 173 { 174 const double fSnippetWidth( 175 getTextLayouter().getTextWidth( 176 getCastedSource()->getText(), 177 nIndex, 178 nLength)); 179 180 if(basegfx::fTools::more(fSnippetWidth, 0.0)) 181 { 182 const ::rtl::OUString aText(getCastedSource()->getText()); 183 const ::rtl::OUString aTrimmedChars(aText.copy(nIndex, nLength).trim()); 184 const double fEndPos(mfPosition + fSnippetWidth); 185 186 if(aTrimmedChars.getLength() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0)) 187 { 188 const double fHalfSnippetWidth(fSnippetWidth * 0.5); 189 190 advanceToPosition(mfPosition + fHalfSnippetWidth); 191 192 // create representation for this snippet 193 bRetval = true; 194 195 // get target position and tangent in that pint 196 basegfx::B2DPoint aPosition(0.0, 0.0); 197 basegfx::B2DVector aTangent(0.0, 1.0); 198 199 if(mfPosition < 0.0) 200 { 201 // snippet center is left of first segment, but right edge is on it (SVG allows that) 202 aTangent = maCurrentSegment.getTangent(0.0); 203 aTangent.normalize(); 204 aPosition = maCurrentSegment.getStartPoint() + (aTangent * (mfPosition - mfSegmentStartPosition)); 205 } 206 else if(mfPosition > mfBasegfxPathLength) 207 { 208 // snippet center is right of last segment, but left edge is on it (SVG allows that) 209 aTangent = maCurrentSegment.getTangent(1.0); 210 aTangent.normalize(); 211 aPosition = maCurrentSegment.getEndPoint() + (aTangent * (mfPosition - mfSegmentStartPosition)); 212 } 213 else 214 { 215 // snippet center inside segment, interpolate 216 double fBezierDistance(mfPosition - mfSegmentStartPosition); 217 218 if(getB2DCubicBezierHelper()) 219 { 220 // use B2DCubicBezierHelper to bridge the non-linear gap between 221 // length and bezier distances (if it's a bezier segment) 222 fBezierDistance = getB2DCubicBezierHelper()->distanceToRelative(fBezierDistance); 223 } 224 else 225 { 226 // linear relationship, make relative to segment length 227 fBezierDistance = fBezierDistance / mfCurrentSegmentLength; 228 } 229 230 aPosition = maCurrentSegment.interpolatePoint(fBezierDistance); 231 aTangent = maCurrentSegment.getTangent(fBezierDistance); 232 aTangent.normalize(); 233 } 234 235 // detect evtl. hor/ver translations (depends on text direction) 236 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0)); 237 const basegfx::B2DVector aOffset(aBasePoint - mrTextStart); 238 239 if(!basegfx::fTools::equalZero(aOffset.getY())) 240 { 241 // ...and apply 242 aPosition.setY(aPosition.getY() + aOffset.getY()); 243 } 244 245 // move target position from snippet center to left text start 246 aPosition -= fHalfSnippetWidth * aTangent; 247 248 // remove current translation 249 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY()); 250 251 // rotate due to tangent 252 rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX())); 253 254 // add new translation 255 rNewTransform.translate(aPosition.getX(), aPosition.getY()); 256 } 257 258 // advance to end 259 advanceToPosition(fEndPos); 260 } 261 } 262 263 return bRetval; 264 } 265 266 } // end of namespace svgreader 267 } // end of namespace svgio 268 269 ////////////////////////////////////////////////////////////////////////////// 270 271 namespace svgio 272 { 273 namespace svgreader 274 { 275 SvgTextPathNode::SvgTextPathNode( 276 SvgDocument& rDocument, 277 SvgNode* pParent) 278 : SvgNode(SVGTokenTextPath, rDocument, pParent), 279 maSvgStyleAttributes(*this), 280 maXLink(), 281 maStartOffset(), 282 mbMethod(true), 283 mbSpacing(false) 284 { 285 } 286 287 SvgTextPathNode::~SvgTextPathNode() 288 { 289 } 290 291 const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const 292 { 293 return &maSvgStyleAttributes; 294 } 295 296 void SvgTextPathNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent) 297 { 298 // call parent 299 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); 300 301 // read style attributes 302 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent); 303 304 // parse own 305 switch(aSVGToken) 306 { 307 case SVGTokenStyle: 308 { 309 maSvgStyleAttributes.readStyle(aContent); 310 break; 311 } 312 case SVGTokenStartOffset: 313 { 314 SvgNumber aNum; 315 316 if(readSingleNumber(aContent, aNum)) 317 { 318 if(aNum.isPositive()) 319 { 320 setStartOffset(aNum); 321 } 322 } 323 break; 324 } 325 case SVGTokenMethod: 326 { 327 if(aContent.getLength()) 328 { 329 static rtl::OUString aStrAlign(rtl::OUString::createFromAscii("align")); 330 static rtl::OUString aStrStretch(rtl::OUString::createFromAscii("stretch")); 331 332 if(aContent.match(aStrAlign)) 333 { 334 setMethod(true); 335 } 336 else if(aContent.match(aStrStretch)) 337 { 338 setMethod(false); 339 } 340 } 341 break; 342 } 343 case SVGTokenSpacing: 344 { 345 if(aContent.getLength()) 346 { 347 static rtl::OUString aStrAuto(rtl::OUString::createFromAscii("auto")); 348 static rtl::OUString aStrExact(rtl::OUString::createFromAscii("exact")); 349 350 if(aContent.match(aStrAuto)) 351 { 352 setSpacing(true); 353 } 354 else if(aContent.match(aStrExact)) 355 { 356 setSpacing(false); 357 } 358 } 359 break; 360 } 361 case SVGTokenXlinkHref: 362 { 363 const sal_Int32 nLen(aContent.getLength()); 364 365 if(nLen && sal_Unicode('#') == aContent[0]) 366 { 367 maXLink = aContent.copy(1); 368 } 369 break; 370 } 371 default: 372 { 373 break; 374 } 375 } 376 } 377 378 bool SvgTextPathNode::isValid() const 379 { 380 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink)); 381 382 if(!pSvgPathNode) 383 { 384 return false; 385 } 386 387 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath(); 388 389 if(!pPolyPolyPath || !pPolyPolyPath->count()) 390 { 391 return false; 392 } 393 394 const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0)); 395 396 if(!aPolygon.count()) 397 { 398 return false; 399 } 400 401 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon)); 402 403 if(basegfx::fTools::equalZero(fBasegfxPathLength)) 404 { 405 return false; 406 } 407 408 return true; 409 } 410 411 void SvgTextPathNode::decomposePathNode( 412 const drawinglayer::primitive2d::Primitive2DSequence& rPathContent, 413 drawinglayer::primitive2d::Primitive2DSequence& rTarget, 414 const basegfx::B2DPoint& rTextStart) const 415 { 416 if(rPathContent.hasElements()) 417 { 418 const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink)); 419 420 if(pSvgPathNode) 421 { 422 const basegfx::B2DPolyPolygon* pPolyPolyPath = pSvgPathNode->getPath(); 423 424 if(pPolyPolyPath && pPolyPolyPath->count()) 425 { 426 basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0)); 427 428 if(pSvgPathNode->getTransform()) 429 { 430 aPolygon.transform(*pSvgPathNode->getTransform()); 431 } 432 433 const double fBasegfxPathLength(basegfx::tools::getLength(aPolygon)); 434 435 if(!basegfx::fTools::equalZero(fBasegfxPathLength)) 436 { 437 double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user 438 439 if(pSvgPathNode->getPathLength().isSet()) 440 { 441 const double fUserLength(pSvgPathNode->getPathLength().solve(*this, length)); 442 443 if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength)) 444 { 445 fUserToBasegfx = fUserLength / fBasegfxPathLength; 446 } 447 } 448 449 double fPosition(0.0); 450 451 if(getStartOffset().isSet()) 452 { 453 if(Unit_percent == getStartOffset().getUnit()) 454 { 455 // percent are relative to path length 456 fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength; 457 } 458 else 459 { 460 fPosition = getStartOffset().solve(*this, length) * fUserToBasegfx; 461 } 462 } 463 464 if(fPosition >= 0.0) 465 { 466 const sal_Int32 nLength(rPathContent.getLength()); 467 sal_Int32 nCurrent(0); 468 469 while(fPosition < fBasegfxPathLength && nCurrent < nLength) 470 { 471 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = 0; 472 const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]); 473 474 if(xReference.is()) 475 { 476 pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get()); 477 } 478 479 if(pCandidate) 480 { 481 pathTextBreakupHelper aPathTextBreakupHelper( 482 xReference, 483 aPolygon, 484 fBasegfxPathLength, 485 fUserToBasegfx, 486 fPosition, 487 rTextStart); 488 489 const drawinglayer::primitive2d::Primitive2DSequence aResult( 490 aPathTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character)); 491 492 if(aResult.hasElements()) 493 { 494 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult); 495 } 496 497 // advance position to consumed 498 fPosition = aPathTextBreakupHelper.getPosition(); 499 } 500 501 nCurrent++; 502 } 503 } 504 } 505 } 506 } 507 } 508 } 509 510 } // end of namespace svgreader 511 } // end of namespace svgio 512 513 ////////////////////////////////////////////////////////////////////////////// 514 // eof 515