calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / svgio / source / svgreader / svgnode.cxx
blobd45624d3edc638141b022b49d5d1a26dca5b6855
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 <o3tl/string_view.hxx>
25 #include <osl/diagnose.h>
26 #include <tools/urlobj.hxx>
29 namespace svgio::svgreader
31 /// #i125258#
32 bool SvgNode::supportsParentStyle() const
34 return true;
37 const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const
39 return nullptr;
42 void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors(
43 const OUString& rClassStr,
44 const SvgNode& rCurrent,
45 const OUString& aConcatenated)
47 const SvgDocument& rDocument = getDocument();
49 if(!rDocument.hasGlobalCssStyleAttributes())
50 return;
52 const SvgNode* pParent = rCurrent.getParent();
54 // check for ID (highest priority)
55 if(rCurrent.getId())
57 const OUString& rId = *rCurrent.getId();
59 if(rId.getLength())
61 const OUString aNewConcatenated(
62 "#" + rId + aConcatenated);
64 if(pParent)
66 // check for combined selectors at parent firstso that higher specificity will be in front
67 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
70 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
72 if(pNew)
74 // add CssStyle if found
75 maCssStyleVector.push_back(pNew);
80 // check for 'class' references (a list of entries is allowed)
81 if(rCurrent.getClass())
83 const OUString& rClassList = *rCurrent.getClass();
84 const sal_Int32 nLen(rClassList.getLength());
86 if(nLen)
88 std::vector< OUString > aParts;
89 sal_Int32 nPos(0);
90 OUStringBuffer aToken;
92 while(nPos < nLen)
94 const sal_Int32 nInitPos(nPos);
95 copyToLimiter(rClassList, u' ', nPos, aToken, nLen);
96 skip_char(rClassList, u' ', nPos, nLen);
97 const OUString aPart(o3tl::trim(aToken));
98 aToken.setLength(0);
100 if(aPart.getLength())
102 aParts.push_back(aPart);
105 if(nInitPos == nPos)
107 OSL_ENSURE(false, "Could not interpret on current position (!)");
108 nPos++;
112 for(const auto &a : aParts)
114 const OUString aNewConcatenated(
115 "." + a + aConcatenated);
117 if(pParent)
119 // check for combined selectors at parent firstso that higher specificity will be in front
120 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
123 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
125 if(pNew)
127 // add CssStyle if found
128 maCssStyleVector.push_back(pNew);
134 // check for class-dependent references to CssStyles
135 if(rClassStr.isEmpty())
136 return;
138 OUString aNewConcatenated(aConcatenated);
140 if(!rCurrent.getId() && !rCurrent.getClass() && 0 == aConcatenated.indexOf(rClassStr))
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)
145 else
147 aNewConcatenated = rClassStr + aConcatenated;
150 if(pParent)
152 // check for combined selectors at parent firstso that higher specificity will be in front
153 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *pParent, aNewConcatenated);
156 const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aNewConcatenated);
158 if(pNew)
160 // add CssStyle if found
161 maCssStyleVector.push_back(pNew);
165 void SvgNode::fillCssStyleVector(const OUString& rClassStr, const SvgStyleAttributes& rOriginal)
167 OSL_ENSURE(!mbCssStyleVectorBuilt, "OOps, fillCssStyleVector called double ?!?");
168 mbCssStyleVectorBuilt = true;
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
185 // check for local CssStyle with highest priority
186 if(mpLocalCssStyle)
188 // if we have one, use as first entry
189 maCssStyleVector.push_back(mpLocalCssStyle.get());
192 // check the hierarchy for concatenated patterns of Selectors
193 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr, *this, OUString());
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();
199 if(pParent && pParent->getType() == SVGToken::Svg)
201 // #i125329# find Css selector '*', add as last element if found
202 const SvgStyleAttributes* pNew = getDocument().findGlobalCssStyleAttributes("*");
204 if(pNew)
206 // add CssStyle for selector '*' if found
207 maCssStyleVector.push_back(pNew);
211 //local attributes
212 maCssStyleVector.push_back(&rOriginal);
215 const SvgStyleAttributes* SvgNode::checkForCssStyle(const OUString& rClassStr, const SvgStyleAttributes& rOriginal) const
217 if(!mbCssStyleVectorBuilt)
219 // build needed CssStyleVector for local node
220 const_cast< SvgNode* >(this)->fillCssStyleVector(rClassStr, rOriginal);
223 if(maCssStyleVector.empty())
225 // return given original if no CssStyles found
226 return &rOriginal;
228 else
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.
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]);
251 for(size_t a(1); a < maCssStyleVector.size(); a++)
253 SvgStyleAttributes* pNext = const_cast< SvgStyleAttributes* >(maCssStyleVector[a]);
255 pCurrent->setCssStyleParent(pNext);
256 pCurrent = pNext;
259 // return 1st CssStyle as style chain start element (only for the
260 // local element, still no hierarchy used here)
261 return maCssStyleVector[0];
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 maXmlSpace(XmlSpace::NotSet),
274 maDisplay(Display::Inline),
275 mbDecomposing(false),
276 mbCssStyleVectorBuilt(false)
278 OSL_ENSURE(SVGToken::Unknown != maType, "SvgNode with unknown type created (!)");
280 if(pParent)
282 pParent->maChildren.emplace_back(this);
284 else
286 #ifdef DBG_UTIL
287 if(SVGToken::Svg != getType())
289 OSL_ENSURE(false, "No parent for this node (!)");
291 #endif
295 SvgNode::~SvgNode()
299 void SvgNode::readLocalCssStyle(std::u16string_view aContent)
301 if(!mpLocalCssStyle)
303 // create LocalCssStyle if needed but not yet added
304 mpLocalCssStyle.reset(new SvgStyleAttributes(*this));
306 else
308 // 2nd fill would be an error
309 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
312 if(mpLocalCssStyle)
314 // parse and set values to it
315 mpLocalCssStyle->readCssStyle(aContent);
317 else
319 OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)");
323 void SvgNode::parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs)
325 // no longer need to pre-sort moving 'style' entries to the back so that
326 // values get overwritten - that was the previous, not complete solution for
327 // handling the priorities between svg and Css properties
328 const sal_uInt32 nAttributes(xAttribs->getLength());
330 for(sal_uInt32 a(0); a < nAttributes; a++)
332 const OUString aTokenName(xAttribs->getNameByIndex(a));
333 const SVGToken aSVGToken(StrToSVGToken(aTokenName, false));
335 parseAttribute(aTokenName, aSVGToken, xAttribs->getValueByIndex(a));
339 Display getDisplayFromContent(std::u16string_view aContent)
341 if(!aContent.empty())
343 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inline"))
345 return Display::Inline;
347 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
349 return Display::None;
351 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inherit"))
353 return Display::Inherit;
355 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"block"))
357 return Display::Block;
359 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"list-item"))
361 return Display::ListItem;
363 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"run-in"))
365 return Display::RunIn;
367 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"compact"))
369 return Display::Compact;
371 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"marker"))
373 return Display::Marker;
375 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table"))
377 return Display::Table;
379 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inline-table"))
381 return Display::InlineTable;
383 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-row-group"))
385 return Display::TableRowGroup;
387 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-header-group"))
389 return Display::TableHeaderGroup;
391 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-footer-group"))
393 return Display::TableFooterGroup;
395 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-row"))
397 return Display::TableRow;
399 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-column-group"))
401 return Display::TableColumnGroup;
403 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-column"))
405 return Display::TableColumn;
407 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-cell"))
409 return Display::TableCell;
411 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-caption"))
413 return Display::TableCaption;
417 // return the default
418 return Display::Inline;
421 void SvgNode::parseAttribute(const OUString& /*rTokenName*/, SVGToken aSVGToken, const OUString& aContent)
423 switch(aSVGToken)
425 case SVGToken::Id:
427 if(!aContent.isEmpty())
429 setId(aContent);
431 break;
433 case SVGToken::Class:
435 if(!aContent.isEmpty())
437 setClass(aContent);
439 break;
441 case SVGToken::XmlSpace:
443 if(!aContent.isEmpty())
445 if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"default"))
447 setXmlSpace(XmlSpace::Default);
449 else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"preserve"))
451 setXmlSpace(XmlSpace::Preserve);
454 break;
456 case SVGToken::Display:
458 if(!aContent.isEmpty())
460 setDisplay(getDisplayFromContent(aContent));
462 break;
464 default:
466 break;
471 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
473 if (mbDecomposing) //guard against infinite recurse
474 return;
476 if(Display::None == getDisplay())
478 return;
481 if(!bReferenced)
483 if(SVGToken::Defs == getType() ||
484 SVGToken::Symbol == getType() ||
485 SVGToken::ClipPathNode == getType() ||
486 SVGToken::Mask == getType() ||
487 SVGToken::Marker == getType() ||
488 SVGToken::Pattern == getType())
490 // do not decompose defs or symbol nodes (these hold only style-like
491 // objects which may be used by referencing them) except when doing
492 // so controlled referenced
494 // also do not decompose ClipPaths and Masks. These should be embedded
495 // in a defs node (which gets not decomposed by itself), but you never
496 // know
498 // also not directly used are Markers and Patterns, only indirectly used
499 // by reference
501 // #i121656# also do not decompose nodes which have display="none" set
502 // as property
503 return;
507 const auto& rChildren = getChildren();
509 if(rChildren.empty())
510 return;
512 mbDecomposing = true;
514 const sal_uInt32 nCount(rChildren.size());
516 for(sal_uInt32 a(0); a < nCount; a++)
518 SvgNode* pCandidate = rChildren[a].get();
520 if(pCandidate && Display::None != pCandidate->getDisplay())
522 const auto& rGrandChildren = pCandidate->getChildren();
523 const SvgStyleAttributes* pChildStyles = pCandidate->getSvgStyleAttributes();
524 // decompose:
525 // - visible terminal nodes
526 // - all non-terminal nodes (might contain visible nodes down the hierarchy)
527 if( !rGrandChildren.empty() || ( pChildStyles && (Visibility::visible == pChildStyles->getVisibility())) )
529 drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
530 pCandidate->decomposeSvgNode(aNewTarget, bReferenced);
532 if(!aNewTarget.empty())
534 rTarget.append(aNewTarget);
538 else if(!pCandidate)
540 OSL_ENSURE(false, "Null-Pointer in child node list (!)");
544 if(!rTarget.empty())
546 const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
547 if(pStyles)
549 // check if we have Title or Desc
550 const OUString& rTitle = pStyles->getTitle();
551 const OUString& rDesc = pStyles->getDesc();
553 if(!rTitle.isEmpty() || !rDesc.isEmpty())
555 // default object name is empty
556 OUString aObjectName;
558 // use path as object name when outmost element
559 if (SVGToken::Svg == getType())
561 aObjectName = getDocument().getAbsolutePath();
563 if(!aObjectName.isEmpty())
565 INetURLObject aURL(aObjectName);
567 aObjectName = aURL.getName(
568 INetURLObject::LAST_SEGMENT,
569 true,
570 INetURLObject::DecodeMechanism::WithCharset);
574 // pack in ObjectInfoPrimitive2D group
575 drawinglayer::primitive2d::Primitive2DReference xRef(
576 new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
577 std::move(rTarget),
578 aObjectName,
579 rTitle,
580 rDesc));
582 rTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
586 mbDecomposing = false;
589 basegfx::B2DRange SvgNode::getCurrentViewPort() const
591 if(getParent())
593 return getParent()->getCurrentViewPort();
595 else
597 return basegfx::B2DRange(); // return empty B2DRange
601 double SvgNode::getCurrentFontSizeInherited() const
603 if(getParent())
605 return getParent()->getCurrentFontSize();
607 else
609 return 0.0;
613 double SvgNode::getCurrentFontSize() const
615 if(getSvgStyleAttributes())
616 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate);
618 return getCurrentFontSizeInherited();
621 double SvgNode::getCurrentXHeightInherited() const
623 if(getParent())
625 return getParent()->getCurrentXHeight();
627 else
629 return 0.0;
633 double SvgNode::getCurrentXHeight() const
635 if(getSvgStyleAttributes())
636 // for XHeight, use FontSize currently
637 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::ycoordinate);
639 return getCurrentXHeightInherited();
642 void SvgNode::setId(OUString const & rId)
644 if(mpId)
646 mrDocument.removeSvgNodeFromMapper(*mpId);
647 mpId.reset();
650 mpId = rId;
651 mrDocument.addSvgNodeToMapper(*mpId, *this);
654 void SvgNode::setClass(OUString const & rClass)
656 if(mpClass)
658 mrDocument.removeSvgNodeFromMapper(*mpClass);
659 mpClass.reset();
662 mpClass = rClass;
663 mrDocument.addSvgNodeToMapper(*mpClass, *this);
666 XmlSpace SvgNode::getXmlSpace() const
668 if(maXmlSpace != XmlSpace::NotSet)
670 return maXmlSpace;
673 if(getParent())
675 return getParent()->getXmlSpace();
678 // default is XmlSpace::Default
679 return XmlSpace::Default;
682 void SvgNode::accept(Visitor & rVisitor)
684 rVisitor.visit(*this);
686 } // end of namespace svgio
689 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */