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 <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>
33 bool SvgNode::supportsParentStyle() const
38 const SvgStyleAttributes
* SvgNode::getSvgStyleAttributes() const
43 void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors(
44 const OUString
& rClassStr
,
45 const SvgNode
& rCurrent
,
46 const OUString
& aConcatenated
)
48 const SvgDocument
& rDocument
= getDocument();
50 if(!rDocument
.hasGlobalCssStyleAttributes())
53 const SvgNode
* pParent
= rCurrent
.getParent();
55 // check for ID (highest priority)
58 const OUString
& rId
= *rCurrent
.getId();
62 const OUString
aNewConcatenated(
63 "#" + rId
+ aConcatenated
);
67 // check for combined selectors at parent firstso that higher specificity will be in front
68 fillCssStyleVectorUsingHierarchyAndSelectors(rClassStr
, *pParent
, aNewConcatenated
);
71 const SvgStyleAttributes
* pNew
= rDocument
.findGlobalCssStyleAttributes(aNewConcatenated
);
75 // add CssStyle if found
76 maCssStyleVector
.push_back(pNew
);
81 // check for 'class' references (a list of entries is allowed)
82 if(rCurrent
.getClass())
84 const OUString
& rClassList
= *rCurrent
.getClass();
85 const sal_Int32
nLen(rClassList
.getLength());
89 std::vector
< OUString
> aParts
;
91 OUStringBuffer aToken
;
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());
100 if(aPart
.getLength())
102 aParts
.push_back(aPart
);
107 OSL_ENSURE(false, "Could not interpret on current position (!)");
112 for(size_t a(0); a
< aParts
.size(); a
++)
114 const OUString
aNewConcatenated(
115 "." + aParts
[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() == SVGTokenSvg
)
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),
274 maXmlSpace(XmlSpace_notset
),
275 maDisplay(Display_inline
),
277 mbDecomposing(false),
278 mbCssStyleVectorBuilt(false)
280 OSL_ENSURE(SVGTokenUnknown
!= maType
, "SvgNode with unknown type created (!)");
284 pParent
->maChildren
.emplace_back(this);
289 if(SVGTokenSvg
!= getType())
291 OSL_ENSURE(false, "No parent for this node (!)");
301 void SvgNode::readLocalCssStyle(const OUString
& aContent
)
305 // create LocalCssStyle if needed but not yet added
306 mpLocalCssStyle
.reset(new SvgStyleAttributes(*this));
310 // 2nd fill would be an error
311 OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
316 // parse and set values to it
317 mpLocalCssStyle
->readCssStyle(aContent
);
321 OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)");
325 void SvgNode::parseAttributes(const css::uno::Reference
< css::xml::sax::XAttributeList
>& xAttribs
)
327 // no longer need to pre-sort moving 'style' entries to the back so that
328 // values get overwritten - that was the previous, not complete solution for
329 // handling the priorities between svg and Css properties
330 const sal_uInt32
nAttributes(xAttribs
->getLength());
332 for(sal_uInt32
a(0); a
< nAttributes
; a
++)
334 const OUString
aTokenName(xAttribs
->getNameByIndex(a
));
335 const SVGToken
aSVGToken(StrToSVGToken(aTokenName
, false));
337 parseAttribute(aTokenName
, aSVGToken
, xAttribs
->getValueByIndex(a
));
341 Display
getDisplayFromContent(const OUString
& aContent
)
343 if(!aContent
.isEmpty())
345 if(aContent
.startsWith("inline"))
347 return Display_inline
;
349 else if(aContent
.startsWith("none"))
353 else if(aContent
.startsWith("inherit"))
355 return Display_inherit
;
357 else if(aContent
.startsWith("block"))
359 return Display_block
;
361 else if(aContent
.startsWith("list-item"))
363 return Display_list_item
;
365 else if(aContent
.startsWith("run-in"))
367 return Display_run_in
;
369 else if(aContent
.startsWith("compact"))
371 return Display_compact
;
373 else if(aContent
.startsWith("marker"))
375 return Display_marker
;
377 else if(aContent
.startsWith("table"))
379 return Display_table
;
381 else if(aContent
.startsWith("inline-table"))
383 return Display_inline_table
;
385 else if(aContent
.startsWith("table-row-group"))
387 return Display_table_row_group
;
389 else if(aContent
.startsWith("table-header-group"))
391 return Display_table_header_group
;
393 else if(aContent
.startsWith("table-footer-group"))
395 return Display_table_footer_group
;
397 else if(aContent
.startsWith("table-row"))
399 return Display_table_row
;
401 else if(aContent
.startsWith("table-column-group"))
403 return Display_table_column_group
;
405 else if(aContent
.startsWith("table-column"))
407 return Display_table_column
;
409 else if(aContent
.startsWith("table-cell"))
411 return Display_table_cell
;
413 else if(aContent
.startsWith("table-caption"))
415 return Display_table_caption
;
419 // return the default
420 return Display_inline
;
423 void SvgNode::parseAttribute(const OUString
& /*rTokenName*/, SVGToken aSVGToken
, const OUString
& aContent
)
429 if(!aContent
.isEmpty())
437 if(!aContent
.isEmpty())
443 case SVGTokenXmlSpace
:
445 if(!aContent
.isEmpty())
447 if(aContent
.startsWith("default"))
449 setXmlSpace(XmlSpace_default
);
451 else if(aContent
.startsWith("preserve"))
453 setXmlSpace(XmlSpace_preserve
);
458 case SVGTokenDisplay
:
460 if(!aContent
.isEmpty())
462 setDisplay(getDisplayFromContent(aContent
));
473 void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer
& rTarget
, bool bReferenced
) const
475 if (mbDecomposing
) //guard against infinite recurse
478 if(Display_none
== getDisplay())
485 if(SVGTokenDefs
== getType() ||
486 SVGTokenSymbol
== getType() ||
487 SVGTokenClipPathNode
== getType() ||
488 SVGTokenMask
== getType() ||
489 SVGTokenMarker
== getType() ||
490 SVGTokenPattern
== getType())
492 // do not decompose defs or symbol nodes (these hold only style-like
493 // objects which may be used by referencing them) except when doing
494 // so controlled referenced
496 // also do not decompose ClipPaths and Masks. These should be embedded
497 // in a defs node (which gets not decomposed by itself), but you never
500 // also not directly used are Markers and Patterns, only indirectly used
503 // #i121656# also do not decompose nodes which have display="none" set
509 const auto& rChildren
= getChildren();
511 if(rChildren
.empty())
514 mbDecomposing
= true;
516 const sal_uInt32
nCount(rChildren
.size());
518 for(sal_uInt32
a(0); a
< nCount
; a
++)
520 SvgNode
* pCandidate
= rChildren
[a
].get();
522 if(pCandidate
&& Display_none
!= pCandidate
->getDisplay())
524 const auto& rGrandChildren
= pCandidate
->getChildren();
525 const SvgStyleAttributes
* pChildStyles
= pCandidate
->getSvgStyleAttributes();
527 // - visible terminal nodes
528 // - all non-terminal nodes (might contain visible nodes down the hierarchy)
529 if( !rGrandChildren
.empty() || ( pChildStyles
&& (Visibility_visible
== pChildStyles
->getVisibility())) )
531 drawinglayer::primitive2d::Primitive2DContainer aNewTarget
;
532 pCandidate
->decomposeSvgNode(aNewTarget
, bReferenced
);
534 if(!aNewTarget
.empty())
536 rTarget
.append(aNewTarget
);
542 OSL_ENSURE(false, "Null-Pointer in child node list (!)");
548 const SvgStyleAttributes
* pStyles
= getSvgStyleAttributes();
551 // check if we have Title or Desc
552 const OUString
& rTitle
= pStyles
->getTitle();
553 const OUString
& rDesc
= pStyles
->getDesc();
555 if(!rTitle
.isEmpty() || !rDesc
.isEmpty())
557 // default object name is empty
558 OUString aObjectName
;
560 // use path as object name when outmost element
561 if(SVGTokenSvg
== getType())
563 aObjectName
= getDocument().getAbsolutePath();
565 if(!aObjectName
.isEmpty())
567 INetURLObject
aURL(aObjectName
);
569 aObjectName
= aURL
.getName(
570 INetURLObject::LAST_SEGMENT
,
572 INetURLObject::DecodeMechanism::WithCharset
);
576 // pack in ObjectInfoPrimitive2D group
577 const drawinglayer::primitive2d::Primitive2DReference
xRef(
578 new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
584 rTarget
= drawinglayer::primitive2d::Primitive2DContainer
{ xRef
};
588 mbDecomposing
= false;
591 basegfx::B2DRange
SvgNode::getCurrentViewPort() const
595 return getParent()->getCurrentViewPort();
599 return basegfx::B2DRange(); // return empty B2DRange
603 double SvgNode::getCurrentFontSizeInherited() const
607 return getParent()->getCurrentFontSize();
615 double SvgNode::getCurrentFontSize() const
617 if(getSvgStyleAttributes())
618 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, xcoordinate
);
620 return getCurrentFontSizeInherited();
623 double SvgNode::getCurrentXHeightInherited() const
627 return getParent()->getCurrentXHeight();
635 double SvgNode::getCurrentXHeight() const
637 if(getSvgStyleAttributes())
638 // for XHeight, use FontSize currently
639 return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, ycoordinate
);
641 return getCurrentXHeightInherited();
644 void SvgNode::setId(OUString
const & rId
)
648 mrDocument
.removeSvgNodeFromMapper(*mpId
);
653 mrDocument
.addSvgNodeToMapper(*mpId
, *this);
656 void SvgNode::setClass(OUString
const & rClass
)
660 mrDocument
.removeSvgNodeFromMapper(*mpClass
);
665 mrDocument
.addSvgNodeToMapper(*mpClass
, *this);
668 XmlSpace
SvgNode::getXmlSpace() const
670 if(maXmlSpace
!= XmlSpace_notset
)
677 return getParent()->getXmlSpace();
680 // default is XmlSpace_default
681 return XmlSpace_default
;
684 void SvgNode::accept(Visitor
& rVisitor
)
686 rVisitor
.visit(*this);
688 } // end of namespace svgreader
689 } // end of namespace svgio
692 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */