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::TextSimplePortionPrimitive2D& rSource, 181 SvgTextPosition& rSvgTextPosition) 182 : drawinglayer::primitive2d::TextBreakupHelper(rSource), 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 rtl::OUString aFontFamily = rSvgStyleAttributes.getFontFamily().empty() ? 252 rtl::OUString(rtl::OUString::createFromAscii("Times New Roman")) : 253 rSvgStyleAttributes.getFontFamily()[0]; 254 255 // #122324# if the FontFamily name ends on ' embedded' it is probably a re-import 256 // of a SVG export with fiont embedding. Remove this to make font matching work. This 257 // is pretty safe since there should be no font family names ending on ' embedded'. 258 // Remove again when FontEmbedding is implemented in SVG import 259 if(aFontFamily.endsWithAsciiL(" embedded", 9)) 260 { 261 aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9); 262 } 263 264 const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight())); 265 bool bSymbol(false); 266 bool bVertical(false); 267 bool bItalic(FontStyle_italic == rSvgStyleAttributes.getFontStyle() || FontStyle_oblique == rSvgStyleAttributes.getFontStyle()); 268 bool bMonospaced(false); 269 bool bOutline(false); 270 bool bRTL(false); 271 bool bBiDiStrong(false); 272 273 const drawinglayer::attribute::FontAttribute aFontAttribute( 274 aFontFamily, 275 rtl::OUString(), 276 nFontWeight, 277 bSymbol, 278 bVertical, 279 bItalic, 280 bMonospaced, 281 bOutline, 282 bRTL, 283 bBiDiStrong); 284 285 // prepare FontSize 286 double fFontWidth(rSvgStyleAttributes.getFontSize().solve(*this, length)); 287 double fFontHeight(fFontWidth); 288 289 // prepare locale 290 ::com::sun::star::lang::Locale aLocale; 291 292 // prepare TextLayouterDevice 293 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; 294 aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale); 295 296 // prepare TextArray 297 ::std::vector< double > aTextArray(rSvgTextPosition.getX()); 298 299 if(!aTextArray.empty() && aTextArray.size() < nLength) 300 { 301 const sal_uInt32 nArray(aTextArray.size()); 302 303 if(nArray < nLength) 304 { 305 double fStartX(0.0); 306 307 if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX()) 308 { 309 fStartX = rSvgTextPosition.getParent()->getPosition().getX(); 310 } 311 else 312 { 313 fStartX = aTextArray[nArray - 1]; 314 } 315 316 ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray)); 317 aTextArray.reserve(nLength); 318 319 for(sal_uInt32 a(0); a < aExtendArray.size(); a++) 320 { 321 aTextArray.push_back(aExtendArray[a] + fStartX); 322 } 323 } 324 } 325 326 // get current TextPosition and TextWidth in units 327 basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition()); 328 double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength)); 329 330 // check for user-given TextLength 331 if(0.0 != rSvgTextPosition.getTextLength() 332 && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength())) 333 { 334 const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth); 335 336 if(rSvgTextPosition.getLengthAdjust()) 337 { 338 // spacing, need to create and expand TextArray 339 if(aTextArray.empty()) 340 { 341 aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength); 342 } 343 344 for(sal_uInt32 a(0); a < aTextArray.size(); a++) 345 { 346 aTextArray[a] *= fFactor; 347 } 348 } 349 else 350 { 351 // spacing and glyphs, just apply to FontWidth 352 fFontWidth *= fFactor; 353 } 354 355 fTextWidth = rSvgTextPosition.getTextLength(); 356 } 357 358 // get TextAlign 359 TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign()); 360 361 // map TextAnchor to TextAlign, there seems not to be a difference 362 if(TextAnchor_notset != rSvgStyleAttributes.getTextAnchor()) 363 { 364 switch(rSvgStyleAttributes.getTextAnchor()) 365 { 366 case TextAnchor_start: 367 { 368 aTextAlign = TextAlign_left; 369 break; 370 } 371 case TextAnchor_middle: 372 { 373 aTextAlign = TextAlign_center; 374 break; 375 } 376 case TextAnchor_end: 377 { 378 aTextAlign = TextAlign_right; 379 break; 380 } 381 default: 382 { 383 break; 384 } 385 } 386 } 387 388 // apply TextAlign 389 switch(aTextAlign) 390 { 391 case TextAlign_right: 392 { 393 aPosition.setX(aPosition.getX() - fTextWidth); 394 break; 395 } 396 case TextAlign_center: 397 { 398 aPosition.setX(aPosition.getX() - (fTextWidth * 0.5)); 399 break; 400 } 401 case TextAlign_notset: 402 case TextAlign_left: 403 case TextAlign_justify: 404 { 405 // TextAlign_notset, TextAlign_left: nothing to do 406 // TextAlign_justify is not clear currently; handle as TextAlign_left 407 break; 408 } 409 } 410 411 // get fill color 412 const basegfx::BColor aFill(rSvgStyleAttributes.getFill() 413 ? *rSvgStyleAttributes.getFill() 414 : basegfx::BColor(0.0, 0.0, 0.0)); 415 416 // prepare TextTransformation 417 basegfx::B2DHomMatrix aTextTransform; 418 419 aTextTransform.scale(fFontWidth, fFontHeight); 420 aTextTransform.translate(aPosition.getX(), aPosition.getY()); 421 422 // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed 423 const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration()); 424 425 if(TextDecoration_underline == aDeco 426 || TextDecoration_overline == aDeco 427 || TextDecoration_line_through == aDeco) 428 { 429 // get the fill for decroation as described by SVG. We cannot 430 // have different stroke colors/definitions for those, though 431 const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes(); 432 const basegfx::BColor aDecoColor(pDecoDef && pDecoDef->getFill() ? *pDecoDef->getFill() : aFill); 433 434 // create decorated text primitive 435 pRetval = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( 436 aTextTransform, 437 getText(), 438 nIndex, 439 nLength, 440 aTextArray, 441 aFontAttribute, 442 aLocale, 443 aFill, 444 445 // extra props for decorated 446 aDecoColor, 447 aDecoColor, 448 TextDecoration_overline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, 449 TextDecoration_underline == aDeco ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, 450 false, 451 TextDecoration_line_through == aDeco ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE, 452 false, 453 drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE, 454 true, 455 false, 456 drawinglayer::primitive2d::TEXT_RELIEF_NONE, 457 false); 458 } 459 else 460 { 461 // create text primitive 462 pRetval = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( 463 aTextTransform, 464 getText(), 465 nIndex, 466 nLength, 467 aTextArray, 468 aFontAttribute, 469 aLocale, 470 aFill); 471 } 472 473 // advance current TextPosition 474 rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0)); 475 } 476 477 return pRetval; 478 } 479 480 void SvgCharacterNode::decomposeTextWithStyle( 481 drawinglayer::primitive2d::Primitive2DSequence& rTarget, 482 SvgTextPosition& rSvgTextPosition, 483 const SvgStyleAttributes& rSvgStyleAttributes) const 484 { 485 const drawinglayer::primitive2d::Primitive2DReference xRef( 486 createSimpleTextPrimitive( 487 rSvgTextPosition, 488 rSvgStyleAttributes)); 489 490 if(xRef.is()) 491 { 492 if(!rSvgTextPosition.isRotated()) 493 { 494 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 495 } 496 else 497 { 498 // need to apply rotations to each character as given 499 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = 500 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get()); 501 502 if(pCandidate) 503 { 504 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition); 505 const drawinglayer::primitive2d::Primitive2DSequence aResult( 506 alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character)); 507 508 if(aResult.hasElements()) 509 { 510 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult); 511 } 512 513 // also consume for the implied single space 514 rSvgTextPosition.consumeRotation(); 515 } 516 else 517 { 518 OSL_ENSURE(false, "Used primitive is not a text primitive (!)"); 519 } 520 } 521 } 522 } 523 524 void SvgCharacterNode::whiteSpaceHandling() 525 { 526 if(XmlSpace_default == getXmlSpace()) 527 { 528 maText = whiteSpaceHandlingDefault(maText); 529 } 530 else 531 { 532 maText = whiteSpaceHandlingPreserve(maText); 533 } 534 } 535 536 void SvgCharacterNode::addGap() 537 { 538 maText += rtl::OUString(sal_Unicode(' ')); 539 } 540 541 void SvgCharacterNode::concatenate(const rtl::OUString& rText) 542 { 543 maText += rText; 544 } 545 546 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const 547 { 548 if(getText().getLength()) 549 { 550 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes(); 551 552 if(pSvgStyleAttributes) 553 { 554 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes); 555 } 556 } 557 } 558 559 } // end of namespace svgreader 560 } // end of namespace svgio 561 562 ////////////////////////////////////////////////////////////////////////////// 563 564 namespace svgio 565 { 566 namespace svgreader 567 { 568 SvgTextPosition::SvgTextPosition( 569 SvgTextPosition* pParent, 570 const InfoProvider& rInfoProvider, 571 const SvgTextPositions& rSvgTextPositions) 572 : mpParent(pParent), 573 maX(), // computed below 574 maY(), // computed below 575 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)), 576 mfTextLength(0.0), 577 maPosition(), // computed below 578 mnRotationIndex(0), 579 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()), 580 mbAbsoluteX(false), 581 mbAbsoluteY(false) 582 { 583 // get TextLength if provided 584 if(rSvgTextPositions.getTextLength().isSet()) 585 { 586 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length); 587 } 588 589 // SVG does not really define in which units a �rotate� for Text/TSpan is given, 590 // but it seems to be degrees. Convert here to radians 591 if(!maRotate.empty()) 592 { 593 const double fFactor(F_PI / 180.0); 594 595 for(sal_uInt32 a(0); a < maRotate.size(); a++) 596 { 597 maRotate[a] *= fFactor; 598 } 599 } 600 601 // get text positions X 602 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size()); 603 604 if(nSizeX) 605 { 606 // we have absolute positions, get first one as current text position X 607 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate)); 608 mbAbsoluteX = true; 609 610 if(nSizeX > 1) 611 { 612 // fill deltas to maX 613 maX.reserve(nSizeX); 614 615 for(sal_uInt32 a(1); a < nSizeX; a++) 616 { 617 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX()); 618 } 619 } 620 } 621 else 622 { 623 // no absolute position, get from parent 624 if(pParent) 625 { 626 maPosition.setX(pParent->getPosition().getX()); 627 } 628 629 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size()); 630 631 if(nSizeDx) 632 { 633 // relative positions given, translate position derived from parent 634 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate)); 635 636 if(nSizeDx > 1) 637 { 638 // fill deltas to maX 639 maX.reserve(nSizeDx); 640 641 for(sal_uInt32 a(1); a < nSizeDx; a++) 642 { 643 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate)); 644 } 645 } 646 } 647 } 648 649 // get text positions Y 650 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size()); 651 652 if(nSizeY) 653 { 654 // we have absolute positions, get first one as current text position Y 655 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate)); 656 mbAbsoluteX = true; 657 658 if(nSizeY > 1) 659 { 660 // fill deltas to maY 661 maY.reserve(nSizeY); 662 663 for(sal_uInt32 a(1); a < nSizeY; a++) 664 { 665 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY()); 666 } 667 } 668 } 669 else 670 { 671 // no absolute position, get from parent 672 if(pParent) 673 { 674 maPosition.setY(pParent->getPosition().getY()); 675 } 676 677 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size()); 678 679 if(nSizeDy) 680 { 681 // relative positions given, translate position derived from parent 682 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate)); 683 684 if(nSizeDy > 1) 685 { 686 // fill deltas to maY 687 maY.reserve(nSizeDy); 688 689 for(sal_uInt32 a(1); a < nSizeDy; a++) 690 { 691 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate)); 692 } 693 } 694 } 695 } 696 } 697 698 bool SvgTextPosition::isRotated() const 699 { 700 if(maRotate.empty()) 701 { 702 if(getParent()) 703 { 704 return getParent()->isRotated(); 705 } 706 else 707 { 708 return false; 709 } 710 } 711 else 712 { 713 return true; 714 } 715 } 716 717 double SvgTextPosition::consumeRotation() 718 { 719 double fRetval(0.0); 720 721 if(maRotate.empty()) 722 { 723 if(getParent()) 724 { 725 fRetval = mpParent->consumeRotation(); 726 } 727 else 728 { 729 fRetval = 0.0; 730 } 731 } 732 else 733 { 734 const sal_uInt32 nSize(maRotate.size()); 735 736 if(mnRotationIndex < nSize) 737 { 738 fRetval = maRotate[mnRotationIndex++]; 739 } 740 else 741 { 742 fRetval = maRotate[nSize - 1]; 743 } 744 } 745 746 return fRetval; 747 } 748 749 } // end of namespace svgreader 750 } // end of namespace svgio 751 752 ////////////////////////////////////////////////////////////////////////////// 753 // eof 754