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/svgcharacternode.hxx> 26 #include <svgio/svgreader/svgstyleattributes.hxx> 27 #include <drawinglayer/attribute/fontattribute.hxx> 28 #include <drawinglayer/primitive2d/textprimitive2d.hxx> 29 #include <drawinglayer/primitive2d/textlayoutdevice.hxx> 30 #include <drawinglayer/primitive2d/textbreakuphelper.hxx> 31 #include <drawinglayer/primitive2d/groupprimitive2d.hxx> 32 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> 33 34 ////////////////////////////////////////////////////////////////////////////// 35 36 namespace svgio 37 { 38 namespace svgreader 39 { 40 SvgTextPositions::SvgTextPositions() 41 : maX(), 42 maY(), 43 maDx(), 44 maDy(), 45 maRotate(), 46 maTextLength(), 47 mbLengthAdjust(true) 48 { 49 } 50 51 void SvgTextPositions::parseTextPositionAttributes(const rtl::OUString& /*rTokenName*/, SVGToken aSVGToken, const rtl::OUString& aContent) 52 { 53 // parse own 54 switch(aSVGToken) 55 { 56 case SVGTokenX: 57 { 58 if(aContent.getLength()) 59 { 60 SvgNumberVector aVector; 61 62 if(readSvgNumberVector(aContent, aVector)) 63 { 64 setX(aVector); 65 } 66 } 67 break; 68 } 69 case SVGTokenY: 70 { 71 if(aContent.getLength()) 72 { 73 SvgNumberVector aVector; 74 75 if(readSvgNumberVector(aContent, aVector)) 76 { 77 setY(aVector); 78 } 79 } 80 break; 81 } 82 case SVGTokenDx: 83 { 84 if(aContent.getLength()) 85 { 86 SvgNumberVector aVector; 87 88 if(readSvgNumberVector(aContent, aVector)) 89 { 90 setDx(aVector); 91 } 92 } 93 break; 94 } 95 case SVGTokenDy: 96 { 97 if(aContent.getLength()) 98 { 99 SvgNumberVector aVector; 100 101 if(readSvgNumberVector(aContent, aVector)) 102 { 103 setDy(aVector); 104 } 105 } 106 break; 107 } 108 case SVGTokenRotate: 109 { 110 if(aContent.getLength()) 111 { 112 SvgNumberVector aVector; 113 114 if(readSvgNumberVector(aContent, aVector)) 115 { 116 setRotate(aVector); 117 } 118 } 119 break; 120 } 121 case SVGTokenTextLength: 122 { 123 SvgNumber aNum; 124 125 if(readSingleNumber(aContent, aNum)) 126 { 127 if(aNum.isPositive()) 128 { 129 setTextLength(aNum); 130 } 131 } 132 break; 133 } 134 case SVGTokenLengthAdjust: 135 { 136 if(aContent.getLength()) 137 { 138 static rtl::OUString aStrSpacing(rtl::OUString::createFromAscii("spacing")); 139 static rtl::OUString aStrSpacingAndGlyphs(rtl::OUString::createFromAscii("spacingAndGlyphs")); 140 141 if(aContent.match(aStrSpacing)) 142 { 143 setLengthAdjust(true); 144 } 145 else if(aContent.match(aStrSpacingAndGlyphs)) 146 { 147 setLengthAdjust(false); 148 } 149 } 150 break; 151 } 152 default: 153 { 154 break; 155 } 156 } 157 } 158 159 } // end of namespace svgreader 160 } // end of namespace svgio 161 162 ////////////////////////////////////////////////////////////////////////////// 163 164 namespace svgio 165 { 166 namespace svgreader 167 { 168 class localTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper 169 { 170 private: 171 SvgTextPosition& mrSvgTextPosition; 172 173 protected: 174 /// allow user callback to allow changes to the new TextTransformation. Default 175 /// does nothing. 176 virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength); 177 178 public: 179 localTextBreakupHelper( 180 const drawinglayer::primitive2d::Primitive2DReference& rxSource, 181 SvgTextPosition& rSvgTextPosition) 182 : drawinglayer::primitive2d::TextBreakupHelper(rxSource), 183 mrSvgTextPosition(rSvgTextPosition) 184 { 185 } 186 }; 187 188 bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/) 189 { 190 const double fRotation(mrSvgTextPosition.consumeRotation()); 191 192 if(0.0 != fRotation) 193 { 194 const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0)); 195 196 rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY()); 197 rNewTransform.rotate(fRotation); 198 rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY()); 199 } 200 201 return true; 202 } 203 204 } // end of namespace svgreader 205 } // end of namespace svgio 206 207 ////////////////////////////////////////////////////////////////////////////// 208 209 namespace svgio 210 { 211 namespace svgreader 212 { 213 SvgCharacterNode::SvgCharacterNode( 214 SvgDocument& rDocument, 215 SvgNode* pParent, 216 const rtl::OUString& rText) 217 : SvgNode(SVGTokenCharacter, rDocument, pParent), 218 maText(rText) 219 { 220 } 221 222 SvgCharacterNode::~SvgCharacterNode() 223 { 224 } 225 226 const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const 227 { 228 // no own style, use parent's 229 if(getParent()) 230 { 231 return getParent()->getSvgStyleAttributes(); 232 } 233 else 234 { 235 return 0; 236 } 237 } 238 239 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* SvgCharacterNode::createSimpleTextPrimitive( 240 SvgTextPosition& rSvgTextPosition, 241 const SvgStyleAttributes& rSvgStyleAttributes) const 242 { 243 // prepare retval, index and length 244 drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pRetval = 0; 245 sal_uInt32 nIndex(0); 246 sal_uInt32 nLength(getText().getLength()); 247 248 if(nLength) 249 { 250 // prepare FontAttribute 251 const rtl::OUString aFontFamily = rSvgStyleAttributes.getFontFamily().empty() ? 252 rtl::OUString(rtl::OUString::createFromAscii("Times New Roman")) : 253 rSvgStyleAttributes.getFontFamily()[0]; 254 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight())); 255 bool bSymbol(false); 256 bool bVertical(false); 257 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle()); 258 bool bMonospaced(false); 259 bool bOutline(false); 260 bool bRTL(false); 261 bool bBiDiStrong(false); 262 263 const drawinglayer::attribute::FontAttribute aFontAttribute( 264 aFontFamily, 265 rtl::OUString(), 266 nFontWeight, 267 bSymbol, 268 bVertical, 269 bItalic, 270 bMonospaced, 271 bOutline, 272 bRTL, 273 bBiDiStrong); 274 275 // prepare FontSize 276 double fFontWidth(rSvgStyleAttributes.getFontSize().solve(*this, length)); 277 double fFontHeight(fFontWidth); 278 279 // prepare locale 280 ::com::sun::star::lang::Locale aLocale; 281 282 // prepare TextLayouterDevice 283 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; 284 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale); 285 286 // prepare TextArray 287 ::std::vector< double > aTextArray(rSvgTextPosition.getX()); 288 289 if(!aTextArray.empty() && aTextArray.size() < nLength) 290 { 291 const sal_uInt32 nArray(aTextArray.size()); 292 293 if(nArray < nLength) 294 { 295 double fStartX(0.0); 296 297 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX()) 298 { 299 fStartX = rSvgTextPosition.getParent()->getPosition().getX(); 300 } 301 else 302 { 303 fStartX = aTextArray[nArray - 1]; 304 } 305 306 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray)); 307 aTextArray.reserve(nLength); 308 309 for(sal_uInt32 a(0); a < aExtendArray.size(); a++) 310 { 311 aTextArray.push_back(aExtendArray[a] + fStartX); 312 } 313 } 314 } 315 316 // get current TextPosition and TextWidth in units 317 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition()); 318 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength)); 319 320 // check for user-given TextLength 321 if(0.0 != rSvgTextPosition.getTextLength() 322 && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength())) 323 { 324 const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth); 325 326 if(rSvgTextPosition.getLengthAdjust()) 327 { 328 // spacing, need to create and expand TextArray 329 if(aTextArray.empty()) 330 { 331 aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength); 332 } 333 334 for(sal_uInt32 a(0); a < aTextArray.size(); a++) 335 { 336 aTextArray[a] *= fFactor; 337 } 338 } 339 else 340 { 341 // spacing and glyphs, just apply to FontWidth 342 fFontWidth *= fFactor; 343 } 344 345 fTextWidth = rSvgTextPosition.getTextLength(); 346 } 347 348 // get TextAlign 349 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign()); 350 351 // map TextAnchor to TextAlign, there seems not to be a difference 352 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor()) 353 { 354 switch(rSvgStyleAttributes.getTextAnchor()) 355 { 356 case TextAnchor_start: 357 { 358 aTextAlign = TextAlign_left; 359 break; 360 } 361 case TextAnchor_middle: 362 { 363 aTextAlign = TextAlign_center; 364 break; 365 } 366 case TextAnchor_end: 367 { 368 aTextAlign = TextAlign_right; 369 break; 370 } 371 default: 372 { 373 break; 374 } 375 } 376 } 377 378 // apply TextAlign 379 switch(aTextAlign) 380 { 381 case TextAlign_right: 382 { 383 aPosition.setX(aPosition.getX() - fTextWidth); 384 break; 385 } 386 case TextAlign_center: 387 { 388 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5)); 389 break; 390 } 391 case TextAlign_notset: 392 case TextAlign_left: 393 case TextAlign_justify: 394 { 395 // TextAlign_notset, TextAlign_left: nothing to do 396 // TextAlign_justify is not clear currently; handle as TextAlign_left 397 break; 398 } 399 } 400 401 // get fill color 402 const basegfx::BColor aFill(rSvgStyleAttributes.getFill() 403 ? *rSvgStyleAttributes.getFill() 404 : basegfx::BColor(0.0, 0.0, 0.0)); 405 406 // prepare TextTransformation 407 basegfx::B2DHomMatrix aTextTransform; 408 409 aTextTransform.scale(fFontWidth, fFontHeight); 410 aTextTransform.translate(aPosition.getX(), aPosition.getY()); 411 412 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed 413 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration()); 414 415 if(TextDecoration_underline == aDeco 416 || TextDecoration_overline == aDeco 417 || TextDecoration_line_through == aDeco) 418 { 419 // get the fill for decroation as described by SVG. We cannot 420 // have different stroke colors/definitions for those, though 421 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes(); 422 const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill); 423 424 // create decorated text primitive 425 pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( 426 aTextTransform, 427 getText(), 428 nIndex, 429 nLength, 430 aTextArray, 431 aFontAttribute, 432 aLocale, 433 aFill, 434 435 // extra props for decorated 436 aDecoColor, 437 aDecoColor, 438 TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, 439 TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, 440 false, 441 TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE, 442 false, 443 drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE, 444 true, 445 false, 446 drawinglayer::primitive2d::TEXT_RELIEF_NONE, 447 false); 448 } 449 else 450 { 451 // create text primitive 452 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( 453 aTextTransform, 454 getText(), 455 nIndex, 456 nLength, 457 aTextArray, 458 aFontAttribute, 459 aLocale, 460 aFill); 461 } 462 463 // advance current TextPosition 464 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0)); 465 } 466 467 return pRetval; 468 } 469 470 void SvgCharacterNode::decomposeTextWithStyle( 471 drawinglayer::primitive2d::Primitive2DSequence& rTarget, 472 SvgTextPosition& rSvgTextPosition, 473 const SvgStyleAttributes& rSvgStyleAttributes) const 474 { 475 const drawinglayer::primitive2d::Primitive2DReference xRef( 476 createSimpleTextPrimitive( 477 rSvgTextPosition, 478 rSvgStyleAttributes)); 479 480 if(xRef.is()) 481 { 482 if(!rSvgTextPosition.isRotated()) 483 { 484 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 485 } 486 else 487 { 488 // need to apply rotations to each character as given 489 localTextBreakupHelper alocalTextBreakupHelper(xRef, rSvgTextPosition); 490 const drawinglayer::primitive2d::Primitive2DSequence aResult( 491 alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character)); 492 493 if(aResult.hasElements()) 494 { 495 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult); 496 } 497 498 // also consume for the implied single space 499 rSvgTextPosition.consumeRotation(); 500 } 501 } 502 } 503 504 void SvgCharacterNode::whiteSpaceHandling() 505 { 506 if(XmlSpace_default == getXmlSpace()) 507 { 508 maText = whiteSpaceHandlingDefault(maText); 509 } 510 else 511 { 512 maText = whiteSpaceHandlingPreserve(maText); 513 } 514 } 515 516 void SvgCharacterNode::addGap() 517 { 518 maText += rtl::OUString(sal_Unicode(' ')); 519 } 520 521 void SvgCharacterNode::concatenate(const rtl::OUString& rText) 522 { 523 maText += rText; 524 } 525 526 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const 527 { 528 if(getText().getLength()) 529 { 530 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes(); 531 532 if(pSvgStyleAttributes) 533 { 534 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes); 535 } 536 } 537 } 538 539 } // end of namespace svgreader 540 } // end of namespace svgio 541 542 ////////////////////////////////////////////////////////////////////////////// 543 544 namespace svgio 545 { 546 namespace svgreader 547 { 548 SvgTextPosition::SvgTextPosition( 549 SvgTextPosition* pParent, 550 const InfoProvider& rInfoProvider, 551 const SvgTextPositions& rSvgTextPositions) 552 : mpParent(pParent), 553 maX(), // computed below 554 maY(), // computed below 555 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)), 556 mfTextLength(0.0), 557 maPosition(), // computed below 558 mnRotationIndex(0), 559 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()), 560 mbAbsoluteX(false), 561 mbAbsoluteY(false) 562 { 563 // get TextLength if provided 564 if(rSvgTextPositions.getTextLength().isSet()) 565 { 566 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length); 567 } 568 569 // SVG does not really define in which units a �rotate� for Text/TSpan is given, 570 // but it seems to be degrees. Convert here to radians 571 if(!maRotate.empty()) 572 { 573 const double fFactor(F_PI / 180.0); 574 575 for(sal_uInt32 a(0); a < maRotate.size(); a++) 576 { 577 maRotate[a] *= fFactor; 578 } 579 } 580 581 // get text positions X 582 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size()); 583 584 if(nSizeX) 585 { 586 // we have absolute positions, get first one as current text position X 587 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate)); 588 mbAbsoluteX = true; 589 590 if(nSizeX > 1) 591 { 592 // fill deltas to maX 593 maX.reserve(nSizeX); 594 595 for(sal_uInt32 a(1); a < nSizeX; a++) 596 { 597 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX()); 598 } 599 } 600 } 601 else 602 { 603 // no absolute position, get from parent 604 if(pParent) 605 { 606 maPosition.setX(pParent->getPosition().getX()); 607 } 608 609 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size()); 610 611 if(nSizeDx) 612 { 613 // relative positions given, translate position derived from parent 614 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate)); 615 616 if(nSizeDx > 1) 617 { 618 // fill deltas to maX 619 maX.reserve(nSizeDx); 620 621 for(sal_uInt32 a(1); a < nSizeDx; a++) 622 { 623 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate)); 624 } 625 } 626 } 627 } 628 629 // get text positions Y 630 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size()); 631 632 if(nSizeY) 633 { 634 // we have absolute positions, get first one as current text position Y 635 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate)); 636 mbAbsoluteX = true; 637 638 if(nSizeY > 1) 639 { 640 // fill deltas to maY 641 maY.reserve(nSizeY); 642 643 for(sal_uInt32 a(1); a < nSizeY; a++) 644 { 645 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY()); 646 } 647 } 648 } 649 else 650 { 651 // no absolute position, get from parent 652 if(pParent) 653 { 654 maPosition.setY(pParent->getPosition().getY()); 655 } 656 657 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size()); 658 659 if(nSizeDy) 660 { 661 // relative positions given, translate position derived from parent 662 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate)); 663 664 if(nSizeDy > 1) 665 { 666 // fill deltas to maY 667 maY.reserve(nSizeDy); 668 669 for(sal_uInt32 a(1); a < nSizeDy; a++) 670 { 671 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate)); 672 } 673 } 674 } 675 } 676 } 677 678 bool SvgTextPosition::isRotated() const 679 { 680 if(maRotate.empty()) 681 { 682 if(getParent()) 683 { 684 return getParent()->isRotated(); 685 } 686 else 687 { 688 return false; 689 } 690 } 691 else 692 { 693 return true; 694 } 695 } 696 697 double SvgTextPosition::consumeRotation() 698 { 699 double fRetval(0.0); 700 701 if(maRotate.empty()) 702 { 703 if(getParent()) 704 { 705 fRetval = mpParent->consumeRotation(); 706 } 707 else 708 { 709 fRetval = 0.0; 710 } 711 } 712 else 713 { 714 const sal_uInt32 nSize(maRotate.size()); 715 716 if(mnRotationIndex < nSize) 717 { 718 fRetval = maRotate[mnRotationIndex++]; 719 } 720 else 721 { 722 fRetval = maRotate[nSize - 1]; 723 } 724 } 725 726 return fRetval; 727 } 728 729 } // end of namespace svgreader 730 } // end of namespace svgio 731 732 ////////////////////////////////////////////////////////////////////////////// 733 // eof 734