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