bump product version to 7.2.5.1
[LibreOffice.git] / svgio / source / svgreader / svgnode.cxx
blob44c32392028166b83071583b226cfe05634806f5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <svgdocument.hxx>
21 #include <svgnode.hxx>
22 #include <svgstyleattributes.hxx>
23 #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
24 #include <tools/urlobj.hxx>
27 namespace svgio::svgreader
29 /// #i125258#
30 bool SvgNode::supportsParentStyle() const
32 return true;
35 const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const
37 return nullptr;
40 void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors(
41 const OUString& rClassStr,
42 const SvgNode& rCurrent,
43 const OUString& aConcatenated)
45 const SvgDocument& rDocument = getDocument();
47 if(!rDocument.hasGlobalCssStyleAttributes())
48 return;
50 const SvgNode* pParent = rCurrent.getParent();
52 // check for ID (highest priority)
53 if(rCurrent.getId())
55 const OUString& rId = *rCurrent.getId();
57 if(rId.getLength())
59 const OUString aNewConcatenated(
60 "#" + rId + aConcatenated);
62 if(pParent)
64 // check for combined selectors at parent firstso that higher specificity will be in front
65 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
68 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
70 if(pNew)
72 // add CssStyle if found
73 maCssStyleVector.push_back(pNew);
78 // check for 'class' references (a list of entries is allowed)
79 if(rCurrent.getClass())
81 const OUString& rClassList = *rCurrent.getClass();
82 const sal_Int32 nLen(rClassList.getLength());
84 if(nLen)
86 std::vector< OUString > aParts;
87 sal_Int32 nPos(0);
88 OUStringBuffer aToken;
90 while(nPos < nLen)
92 const sal_Int32 nInitPos(nPos);
93 copyToLimiter(rClassList, u' ', nPos, aToken, nLen);
94 skip_char(rClassList, u' ', nPos, nLen);
95 const OUString aPart(aToken.makeStringAndClear().trim());
97 if(aPart.getLength())
99 aParts.push_back(aPart);
102 if(nInitPos == nPos)
104 OSL_ENSURE(false, "Could not interpret on current position (!)");
105 nPos++;
109 for(size_t a(0); a < aParts.size(); a++)
111 const OUString aNewConcatenated(
112 "." + aParts[a] + aConcatenated);
114 if(pParent)
116 // check for combined selectors at parent firstso that higher specificity will be in front
117 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
120 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
122 if(pNew)
124 // add CssStyle if found
125 maCssStyleVector.push_back(pNew);
131 // check for class-dependent references to CssStyles
132 if(rClassStr.isEmpty())
133 return;
135 OUString aNewConcatenated(aConcatenated);
137 if(!rCurrent.getId() && !rCurrent.getClass() && 0 == aConcatenated.indexOf(rClassStr))
139 // no new CssStyle Selector and already starts with rClassStr, do not concatenate;
140 // we pass an 'empty' node (in the sense of CssStyle Selector)
142 else
144 aNewConcatenated = rClassStr + aConcatenated;
147 if(pParent)
149 // check for combined selectors at parent firstso that higher specificity will be in front
150 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
153 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
155 if(pNew)
157 // add CssStyle if found
158 maCssStyleVector.push_back(pNew);
162 void SvgNode::fillCssStyleVector(const OUString& rClassStr, const SvgStyleAttributes& rOriginal)
164 OSL_ENSURE(!mbCssStyleVectorBuilt, "OOps, fillCssStyleVector called double ?!?");
165 mbCssStyleVectorBuilt = true;
167 // #i125293# If we have CssStyles we need to build a linked list of SvgStyleAttributes
168 // which represent this for the current object. There are various methods to
169 // specify CssStyles which need to be taken into account in a given order:
170 // - local CssStyle (independent from global CssStyles at SvgDocument)
171 // - 'id' CssStyle
172 // - 'class' CssStyle(s)
173 // - type-dependent elements (e..g. 'rect' for all rect elements)
174 // - Css selector '*'
175 // - local attributes (rOriginal)
176 // - inherited attributes (up the hierarchy)
177 // The first four will be collected in maCssStyleVector for the current element
178 // (once, this will not change) and be linked in the needed order using the
179 // get/setCssStyleParent at the SvgStyleAttributes which will be used preferred in
180 // member evaluation over the existing parent hierarchy
182 // check for local CssStyle with highest priority
183 if(mpLocalCssStyle)
185 // if we have one, use as first entry
186 maCssStyleVector.push_back(mpLocalCssStyle.get());
189 // check the hierarchy for concatenated patterns of Selectors
190 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *this, OUString());
192 // tdf#99115, Add css selector '*' style only if the element is on top of the hierarchy
193 // meaning its parent is <svg>
194 const SvgNode* pParent = this->getParent();
196 if(pParent && pParent->getType() == SVGToken::Svg)
198 // #i125329# find Css selector '*', add as last element if found
199 const SvgStyleAttributes* pNew = getDocument().findGlobalCssStyleAttributes("*");
201 if(pNew)
203 // add CssStyle for selector '*' if found
204 maCssStyleVector.push_back(pNew);
208 //local attributes
209 maCssStyleVector.push_back(&rOriginal);
212 const SvgStyleAttributes* SvgNode::checkForCssStyle(const OUString& rClassStr, const SvgStyleAttributes& rOriginal) const
214 if(!mbCssStyleVectorBuilt)
216 // build needed CssStyleVector for local node
217 const_cast< SvgNode* >(this)->fillCssStyleVector(rClassStr, rOriginal);
220 if(maCssStyleVector.empty())
222 // return given original if no CssStyles found
223 return &rOriginal;
225 else
227 // #i125293# rOriginal will be the last element in the linked list; use no CssStyleParent
228 // there (reset it) to ensure that the parent hierarchy will be used when it's base
229 // is referenced. This new chaining inserts the CssStyles before the original style,
230 // this makes the whole process much safer since the original style when used will
231 // be not different to the situation without CssStyles; thus loops which may be caused
232 // by trying to use the parent hierarchy of the owner of the style will be avoided
233 // already in this mechanism. It's still good to keep the supportsParentStyle
234 // from #i125258# in place, though.
235 // This chain building using pointers will be done every time when checkForCssStyle
236 // is used (not the search, only the chaining). This is needed since the CssStyles
237 // themselves will be potentially used multiple times. It is not expensive since it's
238 // only changing some pointers.
239 // The alternative would be to create the style hierarchy for every element (or even
240 // for the element containing the hierarchy) in a vector of pointers and to use that.
241 // Resetting the CssStyleParent on rOriginal is probably not needed
242 // but simply safer to do.
244 // loop over the existing CssStyles and link them. There is a first one, take
245 // as current
246 SvgStyleAttributes* pCurrent = const_cast< SvgStyleAttributes* >(maCssStyleVector[0]);
248 for(size_t a(1); a < maCssStyleVector.size(); a++)
250 SvgStyleAttributes* pNext = const_cast< SvgStyleAttributes* >(maCssStyleVector[a]);
252 pCurrent->setCssStyleParent(pNext);
253 pCurrent = pNext;
256 // return 1st CssStyle as style chain start element (only for the
257 // local element, still no hierarchy used here)
258 return maCssStyleVector[0];
262 SvgNode::SvgNode(
263 SVGToken aType,
264 SvgDocument& rDocument,
265 SvgNode* pParent)
266 : maType(aType),
267 mrDocument(rDocument),
268 mpParent(pParent),
269 mpAlternativeParent(nullptr),
270 maChildren(),
271 maXmlSpace(XmlSpace::NotSet),
272 maDisplay(Display::Inline),
273 maCssStyleVector(),
274 mbDecomposing(false),
275 mbCssStyleVectorBuilt(false)
277 OSL_ENSURE(SVGToken::Unknown != maType, "SvgNode with unknown type created (!)");
279 if(pParent)
281 pParent->maChildren.emplace_back(this);
283 else
285 #ifdef DBG_UTIL
286 if(SVGToken::Svg != getType())
288 OSL_ENSURE(false, "No parent for this node (!)");
290 #endif
294 SvgNode::~SvgNode()
298 void SvgNode::readLocalCssStyle(const OUString& aContent)
300 if(!mpLocalCssStyle)
302 // create LocalCssStyle if needed but not yet added
303 mpLocalCssStyle.reset(new SvgStyleAttributes(*this));
305 else
307 // 2nd fill would be an error
308 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
311 if(mpLocalCssStyle)
313 // parse and set values to it
314 mpLocalCssStyle->readCssStyle(aContent);
316 else
318 OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)");
322 void SvgNode::parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs)
324 // no longer need to pre-sort moving 'style' entries to the back so that
325 // values get overwritten - that was the previous, not complete solution for
326 // handling the priorities between svg and Css properties
327 const sal_uInt32 nAttributes(xAttribs->getLength());
329 for(sal_uInt32 a(0); a < nAttributes; a++)
331 const OUString aTokenName(xAttribs->getNameByIndex(a));
332 const SVGToken aSVGToken(StrToSVGToken(aTokenName, false));
334 parseAttribute(aTokenName, aSVGToken, xAttribs->getValueByIndex(a));
338 Display getDisplayFromContent(const OUString& aContent)
340 if(!aContent.isEmpty())
342 if(aContent.startsWith("inline"))
344 return Display::Inline;
346 else if(aContent.startsWith("none"))
348 return Display::None;
350 else if(aContent.startsWith("inherit"))
352 return Display::Inherit;
354 else if(aContent.startsWith("block"))
356 return Display::Block;
358 else if(aContent.startsWith("list-item"))
360 return Display::ListItem;
362 else if(aContent.startsWith("run-in"))
364 return Display::RunIn;
366 else if(aContent.startsWith("compact"))
368 return Display::Compact;
370 else if(aContent.startsWith("marker"))
372 return Display::Marker;
374 else if(aContent.startsWith("table"))
376 return Display::Table;
378 else if(aContent.startsWith("inline-table"))
380 return Display::InlineTable;
382 else if(aContent.startsWith("table-row-group"))
384 return Display::TableRowGroup;
386 else if(aContent.startsWith("table-header-group"))
388 return Display::TableHeaderGroup;
390 else if(aContent.startsWith("table-footer-group"))
392 return Display::TableFooterGroup;
394 else if(aContent.startsWith("table-row"))
396 return Display::TableRow;
398 else if(aContent.startsWith("table-column-group"))
400 return Display::TableColumnGroup;
402 else if(aContent.startsWith("table-column"))
404 return Display::TableColumn;
406 else if(aContent.startsWith("table-cell"))
408 return Display::TableCell;
410 else if(aContent.startsWith("table-caption"))
412 return Display::TableCaption;
416 // return the default
417 return Display::Inline;
420 void SvgNode::parseAttribute(const OUString& /*rTokenName*/, SVGToken aSVGToken, const OUString& aContent)
422 switch(aSVGToken)
424 case SVGToken::Id:
426 if(!aContent.isEmpty())
428 setId(aContent);
430 break;
432 case SVGToken::Class:
434 if(!aContent.isEmpty())
436 setClass(aContent);
438 break;
440 case SVGToken::XmlSpace:
442 if(!aContent.isEmpty())
444 if(aContent.startsWith("default"))
446 setXmlSpace(XmlSpace::Default);
448 else if(aContent.startsWith("preserve"))
450 setXmlSpace(XmlSpace::Preserve);
453 break;
455 case SVGToken::Display:
457 if(!aContent.isEmpty())
459 setDisplay(getDisplayFromContent(aContent));
461 break;
463 default:
465 break;
470 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
472 if (mbDecomposing) //guard against infinite recurse
473 return;
475 if(Display::None == getDisplay())
477 return;
480 if(!bReferenced)
482 if(SVGToken::Defs == getType() ||
483 SVGToken::Symbol == getType() ||
484 SVGToken::ClipPathNode == getType() ||
485 SVGToken::Mask == getType() ||
486 SVGToken::Marker == getType() ||
487 SVGToken::Pattern == getType())
489 // do not decompose defs or symbol nodes (these hold only style-like
490 // objects which may be used by referencing them) except when doing
491 // so controlled referenced
493 // also do not decompose ClipPaths and Masks. These should be embedded
494 // in a defs node (which gets not decomposed by itself), but you never
495 // know
497 // also not directly used are Markers and Patterns, only indirectly used
498 // by reference
500 // #i121656# also do not decompose nodes which have display="none" set
501 // as property
502 return;
506 const auto& rChildren = getChildren();
508 if(rChildren.empty())
509 return;
511 mbDecomposing = true;
513 const sal_uInt32 nCount(rChildren.size());
515 for(sal_uInt32 a(0); a < nCount; a++)
517 SvgNode* pCandidate = rChildren[a].get();
519 if(pCandidate && Display::None != pCandidate->getDisplay())
521 const auto& rGrandChildren = pCandidate->getChildren();
522 const SvgStyleAttributes* pChildStyles = pCandidate->getSvgStyleAttributes();
523 // decompose:
524 // - visible terminal nodes
525 // - all non-terminal nodes (might contain visible nodes down the hierarchy)
526 if( !rGrandChildren.empty() || ( pChildStyles && (Visibility::visible == pChildStyles->getVisibility())) )
528 drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
529 pCandidate->decomposeSvgNode(aNewTarget, bReferenced);
531 if(!aNewTarget.empty())
533 rTarget.append(aNewTarget);
537 else if(!pCandidate)
539 OSL_ENSURE(false, "Null-Pointer in child node list (!)");
543 if(!rTarget.empty())
545 const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
546 if(pStyles)
548 // check if we have Title or Desc
549 const OUString& rTitle = pStyles->getTitle();
550 const OUString& rDesc = pStyles->getDesc();
552 if(!rTitle.isEmpty() || !rDesc.isEmpty())
554 // default object name is empty
555 OUString aObjectName;
557 // use path as object name when outmost element
558 if (SVGToken::Svg == getType())
560 aObjectName = getDocument().getAbsolutePath();
562 if(!aObjectName.isEmpty())
564 INetURLObject aURL(aObjectName);
566 aObjectName = aURL.getName(
567 INetURLObject::LAST_SEGMENT,
568 true,
569 INetURLObject::DecodeMechanism::WithCharset);
573 // pack in ObjectInfoPrimitive2D group
574 const drawinglayer::primitive2d::Primitive2DReference xRef(
575 new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
576 rTarget,
577 aObjectName,
578 rTitle,
579 rDesc));
581 rTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
585 mbDecomposing = false;
588 basegfx::B2DRange SvgNode::getCurrentViewPort() const
590 if(getParent())
592 return getParent()->getCurrentViewPort();
594 else
596 return basegfx::B2DRange(); // return empty B2DRange
600 double SvgNode::getCurrentFontSizeInherited() const
602 if(getParent())
604 return getParent()->getCurrentFontSize();
606 else
608 return 0.0;
612 double SvgNode::getCurrentFontSize() const
614 if(getSvgStyleAttributes())
615 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate);
617 return getCurrentFontSizeInherited();
620 double SvgNode::getCurrentXHeightInherited() const
622 if(getParent())
624 return getParent()->getCurrentXHeight();
626 else
628 return 0.0;
632 double SvgNode::getCurrentXHeight() const
634 if(getSvgStyleAttributes())
635 // for XHeight, use FontSize currently
636 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::ycoordinate);
638 return getCurrentXHeightInherited();
641 void SvgNode::setId(OUString const & rId)
643 if(mpId)
645 mrDocument.removeSvgNodeFromMapper(*mpId);
646 mpId.reset();
649 mpId = rId;
650 mrDocument.addSvgNodeToMapper(*mpId, *this);
653 void SvgNode::setClass(OUString const & rClass)
655 if(mpClass)
657 mrDocument.removeSvgNodeFromMapper(*mpClass);
658 mpClass.reset();
661 mpClass = rClass;
662 mrDocument.addSvgNodeToMapper(*mpClass, *this);
665 XmlSpace SvgNode::getXmlSpace() const
667 if(maXmlSpace != XmlSpace::NotSet)
669 return maXmlSpace;
672 if(getParent())
674 return getParent()->getXmlSpace();
677 // default is XmlSpace::Default
678 return XmlSpace::Default;
681 void SvgNode::accept(Visitor & rVisitor)
683 rVisitor.visit(*this);
685 } // end of namespace svgio
688 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */