xref: /core/svgio/source/svgreader/svgnode.cxx (revision b8db9688)
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