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# 33 bool SvgNode::supportsParentStyle() const 34 { 35 return true; 36 } 37 38 const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const 39 { 40 return nullptr; 41 } 42 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 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 // - local attributes (rOriginal) 178 // - inherited attributes (up the hierarchy) 179 // The first four will be collected in maCssStyleVector for the current element 180 // (once, this will not change) and be linked in the needed order using the 181 // get/setCssStyleParent at the SvgStyleAttributes which will be used preferred in 182 // member evaluation over the existing parent hierarchy 183 184 // check for local CssStyle with highest priority 185 if(mpLocalCssStyle) 186 { 187 // if we have one, use as first entry 188 maCssStyleVector.push_back(mpLocalCssStyle.get()); 189 } 190 191 // check the hierarchy for concatenated patterns of Selectors 192 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *this, OUString()); 193 194 // #i125329# find Css selector '*', add as last element if found 195 const SvgStyleAttributes* pNew = getDocument().findGlobalCssStyleAttributes("*"); 196 197 if(pNew) 198 { 199 // add CssStyle for selector '*' if found 200 maCssStyleVector.push_back(pNew); 201 } 202 203 //local attributes 204 maCssStyleVector.push_back(&rOriginal); 205 } 206 207 const SvgStyleAttributes* SvgNode::checkForCssStyle(const OUString& rClassStr, const SvgStyleAttributes& rOriginal) const 208 { 209 if(!mbCssStyleVectorBuilt) 210 { 211 // build needed CssStyleVector for local node 212 const_cast< SvgNode* >(this)->fillCssStyleVector(rClassStr, rOriginal); 213 } 214 215 if(maCssStyleVector.empty()) 216 { 217 // return given original if no CssStyles found 218 return &rOriginal; 219 } 220 else 221 { 222 // #i125293# rOriginal will be the last element in the linked list; use no CssStyleParent 223 // there (reset it) to ensure that the parent hierarchy will be used when it's base 224 // is referenced. This new chaining inserts the CssStyles before the original style, 225 // this makes the whole process much safer since the original style when used will 226 // be not different to the situation without CssStyles; thus loops which may be caused 227 // by trying to use the parent hierarchy of the owner of the style will be avoided 228 // already in this mechanism. It's still good to keep the supportsParentStyle 229 // from #i125258# in place, though. 230 // This chain building using pointers will be done every time when checkForCssStyle 231 // is used (not the search, only the chaining). This is needed since the CssStyles 232 // themselves will be potentially used multiple times. It is not expensive since it's 233 // only changing some pointers. 234 // The alternative would be to create the style hierarchy for every element (or even 235 // for the element containing the hierarchy) in a vector of pointers and to use that. 236 // Resetting the CssStyleParent on rOriginal is probably not needed 237 // but simply safer to do. 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(size_t 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 // return 1st CssStyle as style chain start element (only for the 252 // local element, still no hierarchy used here) 253 return maCssStyleVector[0]; 254 } 255 } 256 257 SvgNode::SvgNode( 258 SVGToken aType, 259 SvgDocument& rDocument, 260 SvgNode* pParent) 261 : maType(aType), 262 mrDocument(rDocument), 263 mpParent(pParent), 264 mpAlternativeParent(nullptr), 265 maChildren(), 266 maXmlSpace(XmlSpace_notset), 267 maDisplay(Display_inline), 268 maCssStyleVector(), 269 mbDecomposing(false), 270 mbCssStyleVectorBuilt(false) 271 { 272 OSL_ENSURE(SVGTokenUnknown != maType, "SvgNode with unknown type created (!)"); 273 274 if(pParent) 275 { 276 pParent->maChildren.emplace_back(this); 277 } 278 else 279 { 280 #ifdef DBG_UTIL 281 if(SVGTokenSvg != getType()) 282 { 283 OSL_ENSURE(false, "No parent for this node (!)"); 284 } 285 #endif 286 } 287 } 288 289 SvgNode::~SvgNode() 290 { 291 } 292 293 void SvgNode::readLocalCssStyle(const OUString& aContent) 294 { 295 if(!mpLocalCssStyle) 296 { 297 // create LocalCssStyle if needed but not yet added 298 mpLocalCssStyle.reset(new SvgStyleAttributes(*this)); 299 } 300 else 301 { 302 // 2nd fill would be an error 303 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)"); 304 } 305 306 if(mpLocalCssStyle) 307 { 308 // parse and set values to it 309 mpLocalCssStyle->readCssStyle(aContent); 310 } 311 else 312 { 313 OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)"); 314 } 315 } 316 317 void SvgNode::parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs) 318 { 319 // no longer need to pre-sort moving 'style' entries to the back so that 320 // values get overwritten - that was the previous, not complete solution for 321 // handling the priorities between svg and Css properties 322 const sal_uInt32 nAttributes(xAttribs->getLength()); 323 324 for(sal_uInt32 a(0); a < nAttributes; a++) 325 { 326 const OUString aTokenName(xAttribs->getNameByIndex(a)); 327 const SVGToken aSVGToken(StrToSVGToken(aTokenName, false)); 328 329 parseAttribute(aTokenName, aSVGToken, xAttribs->getValueByIndex(a)); 330 } 331 } 332 333 Display getDisplayFromContent(const OUString& aContent) 334 { 335 if(!aContent.isEmpty()) 336 { 337 if(aContent.startsWith("inline")) 338 { 339 return Display_inline; 340 } 341 else if(aContent.startsWith("none")) 342 { 343 return Display_none; 344 } 345 else if(aContent.startsWith("inherit")) 346 { 347 return Display_inherit; 348 } 349 else if(aContent.startsWith("block")) 350 { 351 return Display_block; 352 } 353 else if(aContent.startsWith("list-item")) 354 { 355 return Display_list_item; 356 } 357 else if(aContent.startsWith("run-in")) 358 { 359 return Display_run_in; 360 } 361 else if(aContent.startsWith("compact")) 362 { 363 return Display_compact; 364 } 365 else if(aContent.startsWith("marker")) 366 { 367 return Display_marker; 368 } 369 else if(aContent.startsWith("table")) 370 { 371 return Display_table; 372 } 373 else if(aContent.startsWith("inline-table")) 374 { 375 return Display_inline_table; 376 } 377 else if(aContent.startsWith("table-row-group")) 378 { 379 return Display_table_row_group; 380 } 381 else if(aContent.startsWith("table-header-group")) 382 { 383 return Display_table_header_group; 384 } 385 else if(aContent.startsWith("table-footer-group")) 386 { 387 return Display_table_footer_group; 388 } 389 else if(aContent.startsWith("table-row")) 390 { 391 return Display_table_row; 392 } 393 else if(aContent.startsWith("table-column-group")) 394 { 395 return Display_table_column_group; 396 } 397 else if(aContent.startsWith("table-column")) 398 { 399 return Display_table_column; 400 } 401 else if(aContent.startsWith("table-cell")) 402 { 403 return Display_table_cell; 404 } 405 else if(aContent.startsWith("table-caption")) 406 { 407 return Display_table_caption; 408 } 409 } 410 411 // return the default 412 return Display_inline; 413 } 414 415 void SvgNode::parseAttribute(const OUString& /*rTokenName*/, SVGToken aSVGToken, const OUString& aContent) 416 { 417 switch(aSVGToken) 418 { 419 case SVGTokenId: 420 { 421 if(!aContent.isEmpty()) 422 { 423 setId(aContent); 424 } 425 break; 426 } 427 case SVGTokenClass: 428 { 429 if(!aContent.isEmpty()) 430 { 431 setClass(aContent); 432 } 433 break; 434 } 435 case SVGTokenXmlSpace: 436 { 437 if(!aContent.isEmpty()) 438 { 439 if(aContent.startsWith("default")) 440 { 441 setXmlSpace(XmlSpace_default); 442 } 443 else if(aContent.startsWith("preserve")) 444 { 445 setXmlSpace(XmlSpace_preserve); 446 } 447 } 448 break; 449 } 450 case SVGTokenDisplay: 451 { 452 if(!aContent.isEmpty()) 453 { 454 setDisplay(getDisplayFromContent(aContent)); 455 } 456 break; 457 } 458 default: 459 { 460 break; 461 } 462 } 463 } 464 465 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const 466 { 467 if (mbDecomposing) //guard against infinite recurse 468 return; 469 470 if(Display_none == getDisplay()) 471 { 472 return; 473 } 474 475 if(!bReferenced) 476 { 477 if(SVGTokenDefs == getType() || 478 SVGTokenSymbol == getType() || 479 SVGTokenClipPathNode == getType() || 480 SVGTokenMask == getType() || 481 SVGTokenMarker == getType() || 482 SVGTokenPattern == getType()) 483 { 484 // do not decompose defs or symbol nodes (these hold only style-like 485 // objects which may be used by referencing them) except when doing 486 // so controlled referenced 487 488 // also do not decompose ClipPaths and Masks. These should be embedded 489 // in a defs node (which gets not decomposed by itself), but you never 490 // know 491 492 // also not directly used are Markers and Patterns, only indirectly used 493 // by reference 494 495 // #i121656# also do not decompose nodes which have display="none" set 496 // as property 497 return; 498 } 499 } 500 501 const auto& rChildren = getChildren(); 502 503 if(rChildren.empty()) 504 return; 505 506 mbDecomposing = true; 507 508 const sal_uInt32 nCount(rChildren.size()); 509 510 for(sal_uInt32 a(0); a < nCount; a++) 511 { 512 SvgNode* pCandidate = rChildren[a].get(); 513 514 if(pCandidate && Display_none != pCandidate->getDisplay()) 515 { 516 const auto& rGrandChildren = pCandidate->getChildren(); 517 const SvgStyleAttributes* pChildStyles = pCandidate->getSvgStyleAttributes(); 518 // decompose: 519 // - visible terminal nodes 520 // - all non-terminal nodes (might contain visible nodes down the hierarchy) 521 if( !rGrandChildren.empty() || ( pChildStyles && (Visibility_visible == pChildStyles->getVisibility())) ) 522 { 523 drawinglayer::primitive2d::Primitive2DContainer aNewTarget; 524 pCandidate->decomposeSvgNode(aNewTarget, bReferenced); 525 526 if(!aNewTarget.empty()) 527 { 528 rTarget.append(aNewTarget); 529 } 530 } 531 } 532 else if(!pCandidate) 533 { 534 OSL_ENSURE(false, "Null-Pointer in child node list (!)"); 535 } 536 } 537 538 if(!rTarget.empty()) 539 { 540 const SvgStyleAttributes* pStyles = getSvgStyleAttributes(); 541 if(pStyles) 542 { 543 // check if we have Title or Desc 544 const OUString& rTitle = pStyles->getTitle(); 545 const OUString& rDesc = pStyles->getDesc(); 546 547 if(!rTitle.isEmpty() || !rDesc.isEmpty()) 548 { 549 // default object name is empty 550 OUString aObjectName; 551 552 // use path as object name when outmost element 553 if(SVGTokenSvg == getType()) 554 { 555 aObjectName = getDocument().getAbsolutePath(); 556 557 if(!aObjectName.isEmpty()) 558 { 559 INetURLObject aURL(aObjectName); 560 561 aObjectName = aURL.getName( 562 INetURLObject::LAST_SEGMENT, 563 true, 564 INetURLObject::DecodeMechanism::WithCharset); 565 } 566 } 567 568 // pack in ObjectInfoPrimitive2D group 569 const drawinglayer::primitive2d::Primitive2DReference xRef( 570 new drawinglayer::primitive2d::ObjectInfoPrimitive2D( 571 rTarget, 572 aObjectName, 573 rTitle, 574 rDesc)); 575 576 rTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef }; 577 } 578 } 579 } 580 mbDecomposing = false; 581 } 582 583 basegfx::B2DRange SvgNode::getCurrentViewPort() const 584 { 585 if(getParent()) 586 { 587 return getParent()->getCurrentViewPort(); 588 } 589 else 590 { 591 return basegfx::B2DRange(); // return empty B2DRange 592 } 593 } 594 595 double SvgNode::getCurrentFontSizeInherited() const 596 { 597 if(getParent()) 598 { 599 return getParent()->getCurrentFontSize(); 600 } 601 else 602 { 603 return 0.0; 604 } 605 } 606 607 double SvgNode::getCurrentFontSize() const 608 { 609 if(getSvgStyleAttributes()) 610 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, xcoordinate); 611 612 return getCurrentFontSizeInherited(); 613 } 614 615 double SvgNode::getCurrentXHeightInherited() const 616 { 617 if(getParent()) 618 { 619 return getParent()->getCurrentXHeight(); 620 } 621 else 622 { 623 return 0.0; 624 } 625 } 626 627 double SvgNode::getCurrentXHeight() const 628 { 629 if(getSvgStyleAttributes()) 630 // for XHeight, use FontSize currently 631 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, ycoordinate); 632 633 return getCurrentXHeightInherited(); 634 } 635 636 void SvgNode::setId(OUString const & rId) 637 { 638 if(mpId) 639 { 640 mrDocument.removeSvgNodeFromMapper(*mpId); 641 mpId.reset(); 642 } 643 644 mpId = rId; 645 mrDocument.addSvgNodeToMapper(*mpId, *this); 646 } 647 648 void SvgNode::setClass(OUString const & rClass) 649 { 650 if(mpClass) 651 { 652 mrDocument.removeSvgNodeFromMapper(*mpClass); 653 mpClass.reset(); 654 } 655 656 mpClass = rClass; 657 mrDocument.addSvgNodeToMapper(*mpClass, *this); 658 } 659 660 XmlSpace SvgNode::getXmlSpace() const 661 { 662 if(maXmlSpace != XmlSpace_notset) 663 { 664 return maXmlSpace; 665 } 666 667 if(getParent()) 668 { 669 return getParent()->getXmlSpace(); 670 } 671 672 // default is XmlSpace_default 673 return XmlSpace_default; 674 } 675 676 void SvgNode::accept(Visitor & rVisitor) 677 { 678 rVisitor.visit(*this); 679 } 680 } // end of namespace svgreader 681 } // end of namespace svgio 682 683 684 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 685
