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 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 const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = 490 dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xRef.get()); 491 492 if(pCandidate) 493 { 494 const localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition); 495 const drawinglayer::primitive2d::Primitive2DSequence aResult( 496 alocalTextBreakupHelper.getResult(drawinglayer::primitive2d::BreakupUnit_character)); 497 498 if(aResult.hasElements()) 499 { 500 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aResult); 501 } 502 503 // also consume for the implied single space 504 rSvgTextPosition.consumeRotation(); 505 } 506 else 507 { 508 OSL_ENSURE(false, "Used primitive is not a text primitive (!)"); 509 } 510 } 511 } 512 } 513 514 void SvgCharacterNode::whiteSpaceHandling() 515 { 516 if(XmlSpace_default == getXmlSpace()) 517 { 518 maText = whiteSpaceHandlingDefault(maText); 519 } 520 else 521 { 522 maText = whiteSpaceHandlingPreserve(maText); 523 } 524 } 525 526 void SvgCharacterNode::addGap() 527 { 528 maText += rtl::OUString(sal_Unicode(' ')); 529 } 530 531 void SvgCharacterNode::concatenate(const rtl::OUString& rText) 532 { 533 maText += rText; 534 } 535 536 void SvgCharacterNode::decomposeText(drawinglayer::primitive2d::Primitive2DSequence& rTarget, SvgTextPosition& rSvgTextPosition) const 537 { 538 if(getText().getLength()) 539 { 540 const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes(); 541 542 if(pSvgStyleAttributes) 543 { 544 decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes); 545 } 546 } 547 } 548 549 } // end of namespace svgreader 550 } // end of namespace svgio 551 552 ////////////////////////////////////////////////////////////////////////////// 553 554 namespace svgio 555 { 556 namespace svgreader 557 { 558 SvgTextPosition::SvgTextPosition( 559 SvgTextPosition* pParent, 560 const InfoProvider& rInfoProvider, 561 const SvgTextPositions& rSvgTextPositions) 562 : mpParent(pParent), 563 maX(), // computed below 564 maY(), // computed below 565 maRotate(solveSvgNumberVector(rSvgTextPositions.getRotate(), rInfoProvider, length)), 566 mfTextLength(0.0), 567 maPosition(), // computed below 568 mnRotationIndex(0), 569 mbLengthAdjust(rSvgTextPositions.getLengthAdjust()), 570 mbAbsoluteX(false), 571 mbAbsoluteY(false) 572 { 573 // get TextLength if provided 574 if(rSvgTextPositions.getTextLength().isSet()) 575 { 576 mfTextLength = rSvgTextPositions.getTextLength().solve(rInfoProvider, length); 577 } 578 579 // SVG does not really define in which units a �rotate� for Text/TSpan is given, 580 // but it seems to be degrees. Convert here to radians 581 if(!maRotate.empty()) 582 { 583 const double fFactor(F_PI / 180.0); 584 585 for(sal_uInt32 a(0); a < maRotate.size(); a++) 586 { 587 maRotate[a] *= fFactor; 588 } 589 } 590 591 // get text positions X 592 const sal_uInt32 nSizeX(rSvgTextPositions.getX().size()); 593 594 if(nSizeX) 595 { 596 // we have absolute positions, get first one as current text position X 597 maPosition.setX(rSvgTextPositions.getX()[0].solve(rInfoProvider, xcoordinate)); 598 mbAbsoluteX = true; 599 600 if(nSizeX > 1) 601 { 602 // fill deltas to maX 603 maX.reserve(nSizeX); 604 605 for(sal_uInt32 a(1); a < nSizeX; a++) 606 { 607 maX.push_back(rSvgTextPositions.getX()[a].solve(rInfoProvider, xcoordinate) - maPosition.getX()); 608 } 609 } 610 } 611 else 612 { 613 // no absolute position, get from parent 614 if(pParent) 615 { 616 maPosition.setX(pParent->getPosition().getX()); 617 } 618 619 const sal_uInt32 nSizeDx(rSvgTextPositions.getDx().size()); 620 621 if(nSizeDx) 622 { 623 // relative positions given, translate position derived from parent 624 maPosition.setX(maPosition.getX() + rSvgTextPositions.getDx()[0].solve(rInfoProvider, xcoordinate)); 625 626 if(nSizeDx > 1) 627 { 628 // fill deltas to maX 629 maX.reserve(nSizeDx); 630 631 for(sal_uInt32 a(1); a < nSizeDx; a++) 632 { 633 maX.push_back(rSvgTextPositions.getDx()[a].solve(rInfoProvider, xcoordinate)); 634 } 635 } 636 } 637 } 638 639 // get text positions Y 640 const sal_uInt32 nSizeY(rSvgTextPositions.getY().size()); 641 642 if(nSizeY) 643 { 644 // we have absolute positions, get first one as current text position Y 645 maPosition.setY(rSvgTextPositions.getY()[0].solve(rInfoProvider, ycoordinate)); 646 mbAbsoluteX = true; 647 648 if(nSizeY > 1) 649 { 650 // fill deltas to maY 651 maY.reserve(nSizeY); 652 653 for(sal_uInt32 a(1); a < nSizeY; a++) 654 { 655 maY.push_back(rSvgTextPositions.getY()[a].solve(rInfoProvider, ycoordinate) - maPosition.getY()); 656 } 657 } 658 } 659 else 660 { 661 // no absolute position, get from parent 662 if(pParent) 663 { 664 maPosition.setY(pParent->getPosition().getY()); 665 } 666 667 const sal_uInt32 nSizeDy(rSvgTextPositions.getDy().size()); 668 669 if(nSizeDy) 670 { 671 // relative positions given, translate position derived from parent 672 maPosition.setY(maPosition.getY() + rSvgTextPositions.getDy()[0].solve(rInfoProvider, ycoordinate)); 673 674 if(nSizeDy > 1) 675 { 676 // fill deltas to maY 677 maY.reserve(nSizeDy); 678 679 for(sal_uInt32 a(1); a < nSizeDy; a++) 680 { 681 maY.push_back(rSvgTextPositions.getDy()[a].solve(rInfoProvider, ycoordinate)); 682 } 683 } 684 } 685 } 686 } 687 688 bool SvgTextPosition::isRotated() const 689 { 690 if(maRotate.empty()) 691 { 692 if(getParent()) 693 { 694 return getParent()->isRotated(); 695 } 696 else 697 { 698 return false; 699 } 700 } 701 else 702 { 703 return true; 704 } 705 } 706 707 double SvgTextPosition::consumeRotation() 708 { 709 double fRetval(0.0); 710 711 if(maRotate.empty()) 712 { 713 if(getParent()) 714 { 715 fRetval = mpParent->consumeRotation(); 716 } 717 else 718 { 719 fRetval = 0.0; 720 } 721 } 722 else 723 { 724 const sal_uInt32 nSize(maRotate.size()); 725 726 if(mnRotationIndex < nSize) 727 { 728 fRetval = maRotate[mnRotationIndex++]; 729 } 730 else 731 { 732 fRetval = maRotate[nSize - 1]; 733 } 734 } 735 736 return fRetval; 737 } 738 739 } // end of namespace svgreader 740 } // end of namespace svgio 741 742 ////////////////////////////////////////////////////////////////////////////// 743 // eof 744