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