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 <tools/urlobj.hxx>
27 namespace svgio::svgreader
30 bool SvgNode::supportsParentStyle() const
35 const SvgStyleAttributes
* SvgNode::getSvgStyleAttributes() const
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())
50 const SvgNode
* pParent
= rCurrent
.getParent();
52 // check for ID (highest priority)
55 const OUString
& rId
= *rCurrent
.getId();
59 const OUString
aNewConcatenated(
60 "#" + rId
+ aConcatenated
);
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
);
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());
86 std::vector
< OUString
> aParts
;
88 OUStringBuffer aToken
;
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());
99 aParts
.push_back(aPart
);
104 OSL_ENSURE(false, "Could not interpret on current position (!)");
109 for(size_t a(0); a
< aParts
.size(); a
++)
111 const OUString
aNewConcatenated(
112 "." + aParts
[a
] + aConcatenated
);
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
);
124 // add CssStyle if found
125 maCssStyleVector
.push_back(pNew
);
131 // check for class-dependent references to CssStyles
132 if(rClassStr
.isEmpty())
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)
144 aNewConcatenated
= rClassStr
+ aConcatenated
;
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
);
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)
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
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("*");
203 // add CssStyle for selector '*' if found
204 maCssStyleVector
.push_back(pNew
);
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
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
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
);
256 // return 1st CssStyle as style chain start element (only for the
257 // local element, still no hierarchy used here)
258 return maCssStyleVector
[0];
264 SvgDocument
& rDocument
,
267 mrDocument(rDocument
),
269 mpAlternativeParent(nullptr),
271 maXmlSpace(XmlSpace::NotSet
),
272 maDisplay(Display::Inline
),
274 mbDecomposing(false),
275 mbCssStyleVectorBuilt(false)
277 OSL_ENSURE(SVGToken::Unknown
!= maType
, "SvgNode with unknown type created (!)");
281 pParent
->maChildren
.emplace_back(this);
286 if(SVGToken::Svg
!= getType())
288 OSL_ENSURE(false, "No parent for this node (!)");
298 void SvgNode::readLocalCssStyle(const OUString
& aContent
)
302 // create LocalCssStyle if needed but not yet added
303 mpLocalCssStyle
.reset(new SvgStyleAttributes(*this));
307 // 2nd fill would be an error
308 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
313 // parse and set values to it
314 mpLocalCssStyle
->readCssStyle(aContent
);
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
)
426 if(!aContent
.isEmpty())
432 case SVGToken::Class
:
434 if(!aContent
.isEmpty())
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
);
455 case SVGToken::Display
:
457 if(!aContent
.isEmpty())
459 setDisplay(getDisplayFromContent(aContent
));
470 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer
& rTarget
, bool bReferenced
) const
472 if (mbDecomposing
) //guard against infinite recurse
475 if(Display::None
== getDisplay())
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
497 // also not directly used are Markers and Patterns, only indirectly used
500 // #i121656# also do not decompose nodes which have display="none" set
506 const auto& rChildren
= getChildren();
508 if(rChildren
.empty())
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();
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
);
539 OSL_ENSURE(false, "Null-Pointer in child node list (!)");
545 const SvgStyleAttributes
* pStyles
= getSvgStyleAttributes();
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
,
569 INetURLObject::DecodeMechanism::WithCharset
);
573 // pack in ObjectInfoPrimitive2D group
574 const drawinglayer::primitive2d::Primitive2DReference
xRef(
575 new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
581 rTarget
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
585 mbDecomposing
= false;
588 basegfx::B2DRange
SvgNode::getCurrentViewPort() const
592 return getParent()->getCurrentViewPort();
596 return basegfx::B2DRange(); // return empty B2DRange
600 double SvgNode::getCurrentFontSizeInherited() const
604 return getParent()->getCurrentFontSize();
612 double SvgNode::getCurrentFontSize() const
614 if(getSvgStyleAttributes())
615 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate
);
617 return getCurrentFontSizeInherited();
620 double SvgNode::getCurrentXHeightInherited() const
624 return getParent()->getCurrentXHeight();
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
)
645 mrDocument
.removeSvgNodeFromMapper(*mpId
);
650 mrDocument
.addSvgNodeToMapper(*mpId
, *this);
653 void SvgNode::setClass(OUString
const & rClass
)
657 mrDocument
.removeSvgNodeFromMapper(*mpClass
);
662 mrDocument
.addSvgNodeToMapper(*mpClass
, *this);
665 XmlSpace
SvgNode::getXmlSpace() const
667 if(maXmlSpace
!= XmlSpace::NotSet
)
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: */