1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
32 bool SvgNode::supportsParentStyle() const
37 const SvgStyleAttributes
* SvgNode::getSvgStyleAttributes() const
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())
52 const SvgNode
* pParent
= rCurrent
.getParent();
54 // check for ID (highest priority)
57 const OUString
& rId
= *rCurrent
.getId();
61 const OUString
aNewConcatenated(
62 "#" + rId
+ aConcatenated
);
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
);
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());
88 std::vector
< OUString
> aParts
;
90 OUStringBuffer aToken
;
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
));
100 if(aPart
.getLength())
102 aParts
.push_back(aPart
);
107 OSL_ENSURE(false, "Could not interpret on current position (!)");
112 for(const auto &a
: aParts
)
114 const OUString
aNewConcatenated(
115 "." + a
+ aConcatenated
);
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
);
127 // add CssStyle if found
128 maCssStyleVector
.push_back(pNew
);
134 // check for class-dependent references to CssStyles
135 if(rClassStr
.isEmpty())
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)
147 aNewConcatenated
= rClassStr
+ aConcatenated
;
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
);
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)
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
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("*");
206 // add CssStyle for selector '*' if found
207 maCssStyleVector
.push_back(pNew
);
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
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
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
);
259 // return 1st CssStyle as style chain start element (only for the
260 // local element, still no hierarchy used here)
261 return maCssStyleVector
[0];
267 SvgDocument
& rDocument
,
270 mrDocument(rDocument
),
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 (!)");
282 pParent
->maChildren
.emplace_back(this);
287 if(SVGToken::Svg
!= getType())
289 OSL_ENSURE(false, "No parent for this node (!)");
299 void SvgNode::readLocalCssStyle(std::u16string_view aContent
)
303 // create LocalCssStyle if needed but not yet added
304 mpLocalCssStyle
.reset(new SvgStyleAttributes(*this));
308 // 2nd fill would be an error
309 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
314 // parse and set values to it
315 mpLocalCssStyle
->readCssStyle(aContent
);
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
)
427 if(!aContent
.isEmpty())
433 case SVGToken::Class
:
435 if(!aContent
.isEmpty())
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
);
456 case SVGToken::Display
:
458 if(!aContent
.isEmpty())
460 setDisplay(getDisplayFromContent(aContent
));
471 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer
& rTarget
, bool bReferenced
) const
473 if (mbDecomposing
) //guard against infinite recurse
476 if(Display::None
== getDisplay())
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
498 // also not directly used are Markers and Patterns, only indirectly used
501 // #i121656# also do not decompose nodes which have display="none" set
507 const auto& rChildren
= getChildren();
509 if(rChildren
.empty())
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();
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
);
540 OSL_ENSURE(false, "Null-Pointer in child node list (!)");
546 const SvgStyleAttributes
* pStyles
= getSvgStyleAttributes();
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
,
570 INetURLObject::DecodeMechanism::WithCharset
);
574 // pack in ObjectInfoPrimitive2D group
575 drawinglayer::primitive2d::Primitive2DReference
xRef(
576 new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
582 rTarget
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
586 mbDecomposing
= false;
589 basegfx::B2DRange
SvgNode::getCurrentViewPort() const
593 return getParent()->getCurrentViewPort();
597 return basegfx::B2DRange(); // return empty B2DRange
601 double SvgNode::getCurrentFontSizeInherited() const
605 return getParent()->getCurrentFontSize();
613 double SvgNode::getCurrentFontSize() const
615 if(getSvgStyleAttributes())
616 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate
);
618 return getCurrentFontSizeInherited();
621 double SvgNode::getCurrentXHeightInherited() const
625 return getParent()->getCurrentXHeight();
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
)
646 mrDocument
.removeSvgNodeFromMapper(*mpId
);
651 mrDocument
.addSvgNodeToMapper(*mpId
, *this);
654 void SvgNode::setClass(OUString
const & rClass
)
658 mrDocument
.removeSvgNodeFromMapper(*mpClass
);
663 mrDocument
.addSvgNodeToMapper(*mpClass
, *this);
666 XmlSpace
SvgNode::getXmlSpace() const
668 if(maXmlSpace
!= XmlSpace::NotSet
)
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: */