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/svgnode.hxx> 26 #include <basegfx/polygon/b2dpolypolygontools.hxx> 27 #include <svgio/svgreader/svgdocument.hxx> 28 #include <svgio/svgreader/svgnode.hxx> 29 #include <svgio/svgreader/svgstyleattributes.hxx> 30 #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> 31 #include <tools/urlobj.hxx> 32 33 ////////////////////////////////////////////////////////////////////////////// 34 35 namespace svgio 36 { 37 namespace svgreader 38 { 39 /// #125258# 40 bool SvgNode::supportsParentStyle() const 41 { 42 return true; 43 } 44 45 const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const 46 { 47 return 0; 48 } 49 50 void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors( 51 const rtl::OUString& rClassStr, 52 const SvgNode& rCurrent, 53 rtl::OUString aConcatenated) 54 { 55 const SvgDocument& rDocument = getDocument(); 56 57 if(rDocument.hasGlobalCssStyleAttributes()) 58 { 59 const SvgNode* pParent = rCurrent.getParent(); 60 61 // check for ID (highest priority) 62 if(rCurrent.getId()) 63 { 64 const rtl::OUString& rId = *rCurrent.getId(); 65 66 if(rId.getLength()) 67 { 68 const rtl::OUString aNewConcatenated( 69 rtl::OUString::createFromAscii("#") + 70 rId + 71 aConcatenated); 72 73 if(pParent) 74 { 75 // check for combined selectors at parent firstso that higher specificity will be in front 76 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated); 77 } 78 79 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated); 80 81 if(pNew) 82 { 83 // add CssStyle if found 84 maCssStyleVector.push_back(pNew); 85 } 86 } 87 } 88 89 // check for 'class' references (a list of entries is allowed) 90 if(rCurrent.getClass()) 91 { 92 const rtl::OUString& rClassList = *rCurrent.getClass(); 93 const sal_Int32 nLen(rClassList.getLength()); 94 95 if(nLen) 96 { 97 std::vector< rtl::OUString > aParts; 98 sal_Int32 nPos(0); 99 rtl::OUStringBuffer aToken; 100 101 while(nPos < nLen) 102 { 103 const sal_Int32 nInitPos(nPos); 104 copyToLimiter(rClassList, sal_Unicode(' '), nPos, aToken, nLen); 105 skip_char(rClassList, sal_Unicode(' '), nPos, nLen); 106 const rtl::OUString aPart(aToken.makeStringAndClear().trim()); 107 108 if(aPart.getLength()) 109 { 110 aParts.push_back(aPart); 111 } 112 113 if(nInitPos == nPos) 114 { 115 OSL_ENSURE(false, "Could not interpret on current position (!)"); 116 nPos++; 117 } 118 } 119 120 for(sal_uInt32 a(0); a < aParts.size(); a++) 121 { 122 const rtl::OUString aNewConcatenated( 123 rtl::OUString::createFromAscii(".") + 124 aParts[a] + 125 aConcatenated); 126 127 if(pParent) 128 { 129 // check for combined selectors at parent firstso that higher specificity will be in front 130 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated); 131 } 132 133 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated); 134 135 if(pNew) 136 { 137 // add CssStyle if found 138 maCssStyleVector.push_back(pNew); 139 } 140 } 141 } 142 } 143 144 // check for class-dependent references to CssStyles 145 if(rClassStr.getLength()) 146 { 147 rtl::OUString aNewConcatenated(aConcatenated); 148 149 if(!rCurrent.getId() && !rCurrent.getClass() && 0 == aConcatenated.indexOf(rClassStr)) 150 { 151 // no new CssStyle Selector and already starts with rClassStr, do not concatenate; 152 // we pass an 'empty' node (in the sense of CssStyle Selector) 153 } 154 else 155 { 156 aNewConcatenated = rClassStr + aConcatenated; 157 } 158 159 if(pParent) 160 { 161 // check for combined selectors at parent firstso that higher specificity will be in front 162 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated); 163 } 164 165 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated); 166 167 if(pNew) 168 { 169 // add CssStyle if found 170 maCssStyleVector.push_back(pNew); 171 } 172 } 173 } 174 } 175 176 void SvgNode::fillCssStyleVector(const rtl::OUString& rClassStr) 177 { 178 OSL_ENSURE(!mbCssStyleVectorBuilt, "OOps, fillCssStyleVector called double ?!?"); 179 mbCssStyleVectorBuilt = true; 180 181 // #125293# If we have CssStyles we need to buuild a linked list of SvgStyleAttributes 182 // which represent this for the current object. There are various methods to 183 // specify CssStyles which need to be taken into account in a given order: 184 // - local CssStyle (independent from global CssStyles at SvgDocument) 185 // - 'id' CssStyle 186 // - 'class' CssStyle(s) 187 // - type-dependent elements (e..g. 'rect' for all rect elements) 188 // - local attributes (rOriginal) 189 // - inherited attributes (up the hierarchy) 190 // The first four will be collected in maCssStyleVector for the current element 191 // (once, this will not change) and be linked in the needed order using the 192 // get/setCssStyleParent at the SvgStyleAttributes which will be used preferred in 193 // member evaluation over the existing parent hierarchy 194 195 // check for local CssStyle with highest priority 196 if(mpLocalCssStyle) 197 { 198 // if we have one, use as first entry 199 maCssStyleVector.push_back(mpLocalCssStyle); 200 } 201 202 // check the hierarchy for concatenated patterns of Selectors 203 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *this, rtl::OUString()); 204 } 205 206 const SvgStyleAttributes* SvgNode::checkForCssStyle(const rtl::OUString& rClassStr, const SvgStyleAttributes& rOriginal) const 207 { 208 if(!mbCssStyleVectorBuilt) 209 { 210 // build needed CssStyleVector for local node 211 const_cast< SvgNode* >(this)->fillCssStyleVector(rClassStr); 212 } 213 214 if(maCssStyleVector.empty()) 215 { 216 // return given original if no CssStlyes found 217 return &rOriginal; 218 } 219 else 220 { 221 // #125293# rOriginal will be the last element in the linked list; use no CssStyleParent 222 // there (reset it) to ensure that the parent hierarchy will be used when it's base 223 // is referenced. This new chaning inserts the CssStyles before the original style, 224 // this makes the whole process much safer since the original style when used will 225 // be not different to the situation without CssStyles; thus loops which may be caused 226 // by trying to use the parent hierarchy of the owner of the style will be avoided 227 // already in this mechanism. It's still good to keep the supportsParentStyle 228 // from #125258# in place, though. 229 // This chain building using pointers will be done every time when checkForCssStyle 230 // is used (not the search, only the chaining). This is needed since the CssStyles 231 // themselves will be potentially used multiple times. It is not expensive since it's 232 // only changing some pointers. 233 // The alternative would be to create the style hierarchy for every element (or even 234 // for the element containing the hierarchy) in a vector of pointers and to use that. 235 // Resetting the CssStyleParent on rOriginal is probably not needeed 236 // but simply safer to do. 237 const_cast< SvgStyleAttributes& >(rOriginal).setCssStyleParent(0); 238 239 // loop over the existing CssStyles and link them. There is a first one, take 240 // as current 241 SvgStyleAttributes* pCurrent = const_cast< SvgStyleAttributes* >(maCssStyleVector[0]); 242 243 for(sal_uInt32 a(1); a < maCssStyleVector.size(); a++) 244 { 245 SvgStyleAttributes* pNext = const_cast< SvgStyleAttributes* >(maCssStyleVector[a]); 246 247 pCurrent->setCssStyleParent(pNext); 248 pCurrent = pNext; 249 } 250 251 // pCurrent is the last used CssStyle, let it point to the original style 252 pCurrent->setCssStyleParent(&rOriginal); 253 254 // return 1st CssStyle as style chain start element (only for the 255 // local element, still no hierarchy used here) 256 return maCssStyleVector[0]; 257 } 258 } 259 260 SvgNode::SvgNode( 261 SVGToken aType, 262 SvgDocument& rDocument, 263 SvgNode* pParent) 264 : maType(aType), 265 mrDocument(rDocument), 266 mpParent(pParent), 267 mpAlternativeParent(0), 268 maChildren(), 269 mpId(0), 270 mpClass(0), 271 maXmlSpace(XmlSpace_notset), 272 maDisplay(Display_inline), 273 maCssStyleVector(), 274 mpLocalCssStyle(0), 275 mbCssStyleVectorBuilt(false) 276 { 277 OSL_ENSURE(SVGTokenUnknown != maType, "SvgNode with unknown type created (!)"); 278 279 if(pParent) 280 { 281 pParent->maChildren.push_back(this); 282 } 283 else 284 { 285 #ifdef DBG_UTIL 286 if(SVGTokenSvg != getType()) 287 { 288 OSL_ENSURE(false, "No parent for this node (!)"); 289 } 290 #endif 291 } 292 } 293 294 SvgNode::~SvgNode() 295 { 296 while(maChildren.size()) 297 { 298 delete maChildren[maChildren.size() - 1]; 299 maChildren.pop_back(); 300 } 301 302 if(mpId) 303 { 304 delete mpId; 305 } 306 307 if(mpClass) 308 { 309 delete mpClass; 310 } 311 312 if(mpLocalCssStyle) 313 { 314 delete mpLocalCssStyle; 315 } 316 } 317 318 void SvgNode::readLocalCssStyle(const rtl::OUString& aContent) 319 { 320 if(!mpLocalCssStyle) 321 { 322 // create LocalCssStyle if needed but not yet added 323 mpLocalCssStyle = new SvgStyleAttributes(*this); 324 } 325 else 326 { 327 // 2nd fill would be an error 328 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)"); 329 } 330 331 if(mpLocalCssStyle) 332 { 333 // parse and set values to it 334 mpLocalCssStyle->readStyle(aContent); 335 } 336 else 337 { 338 OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)"); 339 } 340 } 341 342 void SvgNode::parseAttributes(const com::sun::star::uno::Reference< com::sun::star::xml::sax::XAttributeList >& xAttribs) 343 { 344 // no longer need to pre-sort moving 'style' entries to the back so that 345 // values get overwritten - that was the previous, not complete solution for 346 // handling the priorities between svg and Css properties 347 const sal_uInt32 nAttributes(xAttribs->getLength()); 348 349 for(sal_uInt32 a(0); a < nAttributes; a++) 350 { 351 const ::rtl::OUString aTokenName(xAttribs->getNameByIndex(a)); 352 const SVGToken aSVGToken(StrToSVGToken(aTokenName)); 353 354 parseAttribute(aTokenName, aSVGToken, xAttribs->getValueByIndex(a)); 355 } 356 } 357 358 Display getDisplayFromContent(const rtl::OUString& aContent) 359 { 360 if(aContent.getLength()) 361 { 362 static rtl::OUString aStrInline(rtl::OUString::createFromAscii("inline")); 363 static rtl::OUString aStrBlock(rtl::OUString::createFromAscii("block")); 364 static rtl::OUString aStrList_item(rtl::OUString::createFromAscii("list-item")); 365 static rtl::OUString aStrRun_in(rtl::OUString::createFromAscii("run-in")); 366 static rtl::OUString aStrCompact(rtl::OUString::createFromAscii("compact")); 367 static rtl::OUString aStrMarker(rtl::OUString::createFromAscii("marker")); 368 static rtl::OUString aStrTable(rtl::OUString::createFromAscii("table")); 369 static rtl::OUString aStrInline_table(rtl::OUString::createFromAscii("inline-table")); 370 static rtl::OUString aStrTable_row_group(rtl::OUString::createFromAscii("table-row-group")); 371 static rtl::OUString aStrTable_header_group(rtl::OUString::createFromAscii("table-header-group")); 372 static rtl::OUString aStrTable_footer_group(rtl::OUString::createFromAscii("table-footer-group")); 373 static rtl::OUString aStrTable_row(rtl::OUString::createFromAscii("table-row")); 374 static rtl::OUString aStrTable_column_group(rtl::OUString::createFromAscii("table-column-group")); 375 static rtl::OUString aStrTable_column(rtl::OUString::createFromAscii("table-column")); 376 static rtl::OUString aStrTable_cell(rtl::OUString::createFromAscii("table-cell")); 377 static rtl::OUString aStrTable_caption(rtl::OUString::createFromAscii("table-caption")); 378 static rtl::OUString aStrNone(rtl::OUString::createFromAscii("none")); 379 static rtl::OUString aStrInherit(rtl::OUString::createFromAscii("inherit")); 380 381 if(aContent.match(aStrInline)) 382 { 383 return Display_inline; 384 } 385 else if(aContent.match(aStrNone)) 386 { 387 return Display_none; 388 } 389 else if(aContent.match(aStrInherit)) 390 { 391 return Display_inherit; 392 } 393 else if(aContent.match(aStrBlock)) 394 { 395 return Display_block; 396 } 397 else if(aContent.match(aStrList_item)) 398 { 399 return Display_list_item; 400 } 401 else if(aContent.match(aStrRun_in)) 402 { 403 return Display_run_in; 404 } 405 else if(aContent.match(aStrCompact)) 406 { 407 return Display_compact; 408 } 409 else if(aContent.match(aStrMarker)) 410 { 411 return Display_marker; 412 } 413 else if(aContent.match(aStrTable)) 414 { 415 return Display_table; 416 } 417 else if(aContent.match(aStrInline_table)) 418 { 419 return Display_inline_table; 420 } 421 else if(aContent.match(aStrTable_row_group)) 422 { 423 return Display_table_row_group; 424 } 425 else if(aContent.match(aStrTable_header_group)) 426 { 427 return Display_table_header_group; 428 } 429 else if(aContent.match(aStrTable_footer_group)) 430 { 431 return Display_table_footer_group; 432 } 433 else if(aContent.match(aStrTable_row)) 434 { 435 return Display_table_row; 436 } 437 else if(aContent.match(aStrTable_column_group)) 438 { 439 return Display_table_column_group; 440 } 441 else if(aContent.match(aStrTable_column)) 442 { 443 return Display_table_column; 444 } 445 else if(aContent.match(aStrTable_cell)) 446 { 447 return Display_table_cell; 448 } 449 else if(aContent.match(aStrTable_caption)) 450 { 451 return Display_table_caption; 452 } 453 } 454 455 // return the default 456 return Display_inline; 457 } 458 459 void SvgNode::parseAttribute(const rtl::OUString& /*rTokenName*/, SVGToken aSVGToken, const rtl::OUString& aContent) 460 { 461 switch(aSVGToken) 462 { 463 case SVGTokenId: 464 { 465 if(aContent.getLength()) 466 { 467 setId(&aContent); 468 } 469 break; 470 } 471 case SVGTokenClass: 472 { 473 if(aContent.getLength()) 474 { 475 setClass(&aContent); 476 } 477 break; 478 } 479 case SVGTokenXmlSpace: 480 { 481 if(aContent.getLength()) 482 { 483 static rtl::OUString aStrDefault(rtl::OUString::createFromAscii("default")); 484 static rtl::OUString aStrPreserve(rtl::OUString::createFromAscii("preserve")); 485 486 if(aContent.match(aStrDefault)) 487 { 488 setXmlSpace(XmlSpace_default); 489 } 490 else if(aContent.match(aStrPreserve)) 491 { 492 setXmlSpace(XmlSpace_preserve); 493 } 494 } 495 break; 496 } 497 case SVGTokenDisplay: 498 { 499 if(aContent.getLength()) 500 { 501 setDisplay(getDisplayFromContent(aContent)); 502 } 503 break; 504 } 505 default: 506 { 507 break; 508 } 509 } 510 } 511 512 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const 513 { 514 if(Display_none == getDisplay()) 515 { 516 return; 517 } 518 519 if(!bReferenced) 520 { 521 if(SVGTokenDefs == getType() || 522 SVGTokenSymbol == getType() || 523 SVGTokenClipPathNode == getType() || 524 SVGTokenMask == getType() || 525 SVGTokenMarker == getType() || 526 SVGTokenPattern == getType()) 527 { 528 // do not decompose defs or symbol nodes (these hold only style-like 529 // objects which may be used by referencing them) except when doing 530 // so controlled referenced 531 532 // also do not decompose ClipPaths and Masks. These should be embedded 533 // in a defs node (which gets not decomposed by itself), but you never 534 // know 535 536 // also not directly used are Markers and Patterns, only indirecty used 537 // by reference 538 539 // #121656# also do not decompose nodes which have display="none" set 540 // as property 541 return; 542 } 543 } 544 545 const SvgNodeVector& rChildren = getChildren(); 546 547 if(!rChildren.empty()) 548 { 549 const sal_uInt32 nCount(rChildren.size()); 550 551 for(sal_uInt32 a(0); a < nCount; a++) 552 { 553 SvgNode* pCandidate = rChildren[a]; 554 555 if(pCandidate && Display_none != pCandidate->getDisplay()) 556 { 557 drawinglayer::primitive2d::Primitive2DSequence aNewTarget; 558 559 pCandidate->decomposeSvgNode(aNewTarget, bReferenced); 560 561 if(aNewTarget.hasElements()) 562 { 563 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget); 564 } 565 } 566 else 567 { 568 OSL_ENSURE(false, "Null-Pointer in child node list (!)"); 569 } 570 } 571 572 if(rTarget.hasElements()) 573 { 574 const SvgStyleAttributes* pStyles = getSvgStyleAttributes(); 575 576 if(pStyles) 577 { 578 // check if we have Title or Desc 579 const rtl::OUString& rTitle = pStyles->getTitle(); 580 const rtl::OUString& rDesc = pStyles->getDesc(); 581 582 if(rTitle.getLength() || rDesc.getLength()) 583 { 584 // default object name is empty 585 rtl::OUString aObjectName; 586 587 // use path as object name when outmost element 588 if(SVGTokenSvg == getType()) 589 { 590 aObjectName = getDocument().getAbsolutePath(); 591 592 if(aObjectName.getLength()) 593 { 594 INetURLObject aURL(aObjectName); 595 596 aObjectName = aURL.getName( 597 INetURLObject::LAST_SEGMENT, 598 true, 599 INetURLObject::DECODE_WITH_CHARSET); 600 } 601 } 602 603 // pack in ObjectInfoPrimitive2D group 604 const drawinglayer::primitive2d::Primitive2DReference xRef( 605 new drawinglayer::primitive2d::ObjectInfoPrimitive2D( 606 rTarget, 607 aObjectName, 608 rTitle, 609 rDesc)); 610 611 rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); 612 } 613 } 614 } 615 } 616 } 617 618 const basegfx::B2DRange SvgNode::getCurrentViewPort() const 619 { 620 if(getParent()) 621 { 622 return getParent()->getCurrentViewPort(); 623 } 624 else 625 { 626 return basegfx::B2DRange(); // return empty B2DRange 627 } 628 } 629 630 double SvgNode::getCurrentFontSize() const 631 { 632 if(getSvgStyleAttributes()) 633 { 634 return getSvgStyleAttributes()->getFontSize().solve(*this, xcoordinate); 635 } 636 else if(getParent()) 637 { 638 return getParent()->getCurrentFontSize(); 639 } 640 else 641 { 642 return 0.0; 643 } 644 } 645 646 double SvgNode::getCurrentXHeight() const 647 { 648 if(getSvgStyleAttributes()) 649 { 650 // for XHeight, use FontSize currently 651 return getSvgStyleAttributes()->getFontSize().solve(*this, ycoordinate); 652 } 653 else if(getParent()) 654 { 655 return getParent()->getCurrentXHeight(); 656 } 657 else 658 { 659 return 0.0; 660 } 661 } 662 663 void SvgNode::setId(const rtl::OUString* pfId) 664 { 665 if(mpId) 666 { 667 mrDocument.removeSvgNodeFromMapper(*mpId); 668 delete mpId; 669 mpId = 0; 670 } 671 672 if(pfId) 673 { 674 mpId = new rtl::OUString(*pfId); 675 mrDocument.addSvgNodeToMapper(*mpId, *this); 676 } 677 } 678 679 void SvgNode::setClass(const rtl::OUString* pfClass) 680 { 681 if(mpClass) 682 { 683 mrDocument.removeSvgNodeFromMapper(*mpClass); 684 delete mpClass; 685 mpClass = 0; 686 } 687 688 if(pfClass) 689 { 690 mpClass = new rtl::OUString(*pfClass); 691 mrDocument.addSvgNodeToMapper(*mpClass, *this); 692 } 693 } 694 695 XmlSpace SvgNode::getXmlSpace() const 696 { 697 if(maXmlSpace != XmlSpace_notset) 698 { 699 return maXmlSpace; 700 } 701 702 if(getParent()) 703 { 704 return getParent()->getXmlSpace(); 705 } 706 707 // default is XmlSpace_default 708 return XmlSpace_default; 709 } 710 711 } // end of namespace svgreader 712 } // end of namespace svgio 713 714 ////////////////////////////////////////////////////////////////////////////// 715 // eof 716