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 <svgdocumenthandler.hxx>
21 #include <svgtoken.hxx>
22 #include <svgsvgnode.hxx>
23 #include <svggnode.hxx>
24 #include <svganode.hxx>
25 #include <svgnode.hxx>
26 #include <svgpathnode.hxx>
27 #include <svgrectnode.hxx>
28 #include <svggradientnode.hxx>
29 #include <svggradientstopnode.hxx>
30 #include <svgswitchnode.hxx>
31 #include <svgsymbolnode.hxx>
32 #include <svgusenode.hxx>
33 #include <svgcirclenode.hxx>
34 #include <svgellipsenode.hxx>
35 #include <svglinenode.hxx>
36 #include <svgpolynode.hxx>
37 #include <svgtextnode.hxx>
38 #include <svgcharacternode.hxx>
39 #include <svgtspannode.hxx>
40 #include <svgtrefnode.hxx>
41 #include <svgtextpathnode.hxx>
42 #include <svgstylenode.hxx>
43 #include <svgimagenode.hxx>
44 #include <svgclippathnode.hxx>
45 #include <svgfeblendnode.hxx>
46 #include <svgfecolormatrixnode.hxx>
47 #include <svgfecompositenode.hxx>
48 #include <svgfedropshadownode.hxx>
49 #include <svgfefloodnode.hxx>
50 #include <svgfeimagenode.hxx>
51 #include <svgfegaussianblurnode.hxx>
52 #include <svgfemergenode.hxx>
53 #include <svgfemergenodenode.hxx>
54 #include <svgfeoffsetnode.hxx>
55 #include <svgfilternode.hxx>
56 #include <svgmasknode.hxx>
57 #include <svgmarkernode.hxx>
58 #include <svgpatternnode.hxx>
59 #include <svgtitledescnode.hxx>
60 #include <sal/log.hxx>
61 #include <osl/diagnose.h>
63 #include <com/sun/star/lang/Locale.hpp>
64 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
66 using namespace com::sun::star
;
68 namespace svgio::svgreader
73 using CharacterNodeHandlerFunc
74 = svgio::svgreader::SvgCharacterNode
*(svgio::svgreader::SvgCharacterNode
* pCharNode
,
75 svgio::svgreader::SvgTspanNode
* pParentLine
,
76 svgio::svgreader::SvgCharacterNode
* pLast
);
77 // clean whitespace in text span
78 svgio::svgreader::SvgCharacterNode
* whiteSpaceHandling(svgio::svgreader::SvgCharacterNode
* pCharNode
,
79 svgio::svgreader::SvgTspanNode
* pParentLine
,
80 svgio::svgreader::SvgCharacterNode
* pLast
)
82 pCharNode
->setParentLine(pParentLine
);
83 return pCharNode
->whiteSpaceHandling(pLast
);
86 // set correct widths of text lines
87 svgio::svgreader::SvgCharacterNode
* calcTextLineWidths(svgio::svgreader::SvgCharacterNode
* pCharNode
,
88 svgio::svgreader::SvgTspanNode
* pParentLine
,
89 svgio::svgreader::SvgCharacterNode
* /*pLast*/)
91 if (const SvgStyleAttributes
* pSvgStyleAttributes
= pCharNode
->getSvgStyleAttributes())
93 const drawinglayer::attribute::FontAttribute
aFontAttribute(
94 svgio::svgreader::SvgCharacterNode::getFontAttribute(*pSvgStyleAttributes
));
96 double fFontWidth(pSvgStyleAttributes
->getFontSizeNumber().solve(*pCharNode
));
97 double fFontHeight(fFontWidth
);
99 css::lang::Locale aLocale
;
100 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice
;
101 aTextLayouterDevice
.setFontAttribute(aFontAttribute
, fFontWidth
, fFontHeight
, aLocale
);
102 double fTextWidth
= aTextLayouterDevice
.getTextWidth(pCharNode
->getText(), 0.0,
103 pCharNode
->getText().getLength());
104 pParentLine
->concatenateTextLineWidth(fTextWidth
);
106 return nullptr; // no pLast handling
109 svgio::svgreader::SvgCharacterNode
* walkRecursive(svgio::svgreader::SvgNode
const* pNode
,
110 svgio::svgreader::SvgTspanNode
* pParentLine
,
111 svgio::svgreader::SvgCharacterNode
* pLast
,
112 CharacterNodeHandlerFunc
* pHandlerFunc
)
116 const auto& rChilds
= pNode
->getChildren();
117 const sal_uInt32
nCount(rChilds
.size());
119 for(sal_uInt32
a(0); a
< nCount
; a
++)
121 svgio::svgreader::SvgNode
* pCandidate
= rChilds
[a
].get();
125 switch(pCandidate
->getType())
127 case SVGToken::Character
:
129 svgio::svgreader::SvgCharacterNode
* pCharNode
= static_cast< svgio::svgreader::SvgCharacterNode
* >(pCandidate
);
131 pLast
= pHandlerFunc(pCharNode
, pParentLine
, pLast
);
134 case SVGToken::Tspan
:
136 svgio::svgreader::SvgTspanNode
* pTspanNode
= static_cast< svgio::svgreader::SvgTspanNode
* >(pCandidate
);
138 // If x or y exist it means it's a new line of text
139 if(!pTspanNode
->getX().empty() || !pTspanNode
->getY().empty())
140 pParentLine
= pTspanNode
;
142 // recursively handle subhierarchy
143 pLast
= walkRecursive(pCandidate
, pParentLine
, pLast
, pHandlerFunc
);
146 case SVGToken::TextPath
:
149 // recursively handle subhierarchy
150 pLast
= walkRecursive(pCandidate
, pParentLine
, pLast
, pHandlerFunc
);
155 OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
166 } // end anonymous namespace
168 SvgDocHdl::SvgDocHdl(const OUString
& aAbsolutePath
)
169 : maDocument(aAbsolutePath
),
174 SvgDocHdl::~SvgDocHdl()
178 OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
180 while (mpTarget
->getParent())
181 mpTarget
= const_cast< SvgNode
* >(mpTarget
->getParent());
183 const SvgNodeVector
& rOwnedTopLevels
= maDocument
.getSvgNodeVector();
184 if (std::none_of(rOwnedTopLevels
.begin(), rOwnedTopLevels
.end(),
185 [&](std::unique_ptr
<SvgNode
> const & p
) { return p
.get() == mpTarget
; }))
188 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl destructed with active css style stack entry (!)");
191 void SvgDocHdl::startDocument( )
193 OSL_ENSURE(!mpTarget
, "Already a target at document start (!)");
194 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl startDocument with active css style stack entry (!)");
197 void SvgDocHdl::endDocument( )
199 OSL_ENSURE(!mpTarget
, "Still a target at document end (!)");
200 OSL_ENSURE(maCssContents
.empty(), "SvgDocHdl endDocument with active css style stack entry (!)");
203 void SvgDocHdl::startElement( const OUString
& aName
, const uno::Reference
< xml::sax::XAttributeList
>& xAttribs
)
208 const SVGToken
aSVGToken(StrToSVGToken(aName
, false));
212 /// structural elements
213 case SVGToken::Symbol
:
215 /// new basic node for Symbol. Content gets scanned, but
216 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
217 mpTarget
= new SvgSymbolNode(maDocument
, mpTarget
);
218 mpTarget
->parseAttributes(xAttribs
);
221 case SVGToken::Switch
:
223 /// new node for Switch
224 mpTarget
= new SvgSwitchNode(maDocument
, mpTarget
);
225 mpTarget
->parseAttributes(xAttribs
);
231 /// new node for Defs/G
232 mpTarget
= new SvgGNode(aSVGToken
, maDocument
, mpTarget
);
233 mpTarget
->parseAttributes(xAttribs
);
239 mpTarget
= new SvgSvgNode(maDocument
, mpTarget
);
240 mpTarget
->parseAttributes(xAttribs
);
246 mpTarget
= new SvgUseNode(maDocument
, mpTarget
);
247 mpTarget
->parseAttributes(xAttribs
);
253 mpTarget
= new SvgANode(maDocument
, mpTarget
);
254 mpTarget
->parseAttributes(xAttribs
);
259 case SVGToken::Circle
:
261 /// new node for Circle
262 mpTarget
= new SvgCircleNode(maDocument
, mpTarget
);
263 mpTarget
->parseAttributes(xAttribs
);
266 case SVGToken::Ellipse
:
268 /// new node for Ellipse
269 mpTarget
= new SvgEllipseNode(maDocument
, mpTarget
);
270 mpTarget
->parseAttributes(xAttribs
);
275 /// new node for Line
276 mpTarget
= new SvgLineNode(maDocument
, mpTarget
);
277 mpTarget
->parseAttributes(xAttribs
);
282 /// new node for Path
283 mpTarget
= new SvgPathNode(maDocument
, mpTarget
);
284 mpTarget
->parseAttributes(xAttribs
);
287 case SVGToken::Polygon
:
289 /// new node for Polygon
290 mpTarget
= new SvgPolyNode(aSVGToken
, maDocument
, mpTarget
);
291 mpTarget
->parseAttributes(xAttribs
);
294 case SVGToken::Polyline
:
296 /// new node for Polyline
297 mpTarget
= new SvgPolyNode(aSVGToken
, maDocument
, mpTarget
);
298 mpTarget
->parseAttributes(xAttribs
);
303 /// new node for Rect
304 mpTarget
= new SvgRectNode(maDocument
, mpTarget
);
305 mpTarget
->parseAttributes(xAttribs
);
308 case SVGToken::Image
:
310 /// new node for Image
311 mpTarget
= new SvgImageNode(maDocument
, mpTarget
);
312 mpTarget
->parseAttributes(xAttribs
);
316 /// title and description
317 case SVGToken::Title
:
320 /// new node for Title and/or Desc
321 mpTarget
= new SvgTitleDescNode(aSVGToken
, maDocument
, mpTarget
);
326 case SVGToken::LinearGradient
:
327 case SVGToken::RadialGradient
:
329 mpTarget
= new SvgGradientNode(aSVGToken
, maDocument
, mpTarget
);
330 mpTarget
->parseAttributes(xAttribs
);
337 mpTarget
= new SvgGradientStopNode(maDocument
, mpTarget
);
338 mpTarget
->parseAttributes(xAttribs
);
345 mpTarget
= new SvgTextNode(maDocument
, mpTarget
);
346 mpTarget
->parseAttributes(xAttribs
);
349 case SVGToken::Tspan
:
351 mpTarget
= new SvgTspanNode(aSVGToken
, maDocument
, mpTarget
);
352 mpTarget
->parseAttributes(xAttribs
);
357 mpTarget
= new SvgTrefNode(maDocument
, mpTarget
);
358 mpTarget
->parseAttributes(xAttribs
);
361 case SVGToken::TextPath
:
363 mpTarget
= new SvgTextPathNode(maDocument
, mpTarget
);
364 mpTarget
->parseAttributes(xAttribs
);
368 /// styles (as stylesheets)
369 case SVGToken::Style
:
371 SvgStyleNode
* pNew
= new SvgStyleNode(maDocument
, mpTarget
);
374 // #i125326# there are attributes, read them. This will set isTextCss to false if
375 // type attribute is different to "text/css"
376 mpTarget
->parseAttributes(xAttribs
);
378 if(pNew
->isTextCss())
380 // if it is a Css style, allow reading text between the start and end tag (see
381 // SvgDocHdl::characters for details)
382 maCssContents
.emplace_back();
387 /// structural elements clip-path and mask. Content gets scanned, but
388 /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
389 case SVGToken::ClipPathNode
:
391 /// new node for ClipPath
392 mpTarget
= new SvgClipPathNode(maDocument
, mpTarget
);
393 mpTarget
->parseAttributes(xAttribs
);
398 /// new node for Mask
399 mpTarget
= new SvgMaskNode(maDocument
, mpTarget
);
400 mpTarget
->parseAttributes(xAttribs
);
403 case SVGToken::FeBlend
:
405 /// new node for feBlend
406 mpTarget
= new SvgFeBlendNode(maDocument
, mpTarget
);
407 mpTarget
->parseAttributes(xAttribs
);
410 case SVGToken::FeColorMatrix
:
412 /// new node for feColorMatrix
413 mpTarget
= new SvgFeColorMatrixNode(maDocument
, mpTarget
);
414 mpTarget
->parseAttributes(xAttribs
);
417 case SVGToken::FeComposite
:
419 /// new node for feComposite
420 mpTarget
= new SvgFeCompositeNode(maDocument
, mpTarget
);
421 mpTarget
->parseAttributes(xAttribs
);
424 case SVGToken::FeDropShadow
:
426 /// new node for feDropShadow
427 mpTarget
= new SvgFeDropShadowNode(maDocument
, mpTarget
);
428 mpTarget
->parseAttributes(xAttribs
);
431 case SVGToken::FeFlood
:
433 /// new node for feFlood
434 mpTarget
= new SvgFeFloodNode(maDocument
, mpTarget
);
435 mpTarget
->parseAttributes(xAttribs
);
438 case SVGToken::FeImage
:
440 /// new node for feImage
441 mpTarget
= new SvgFeImageNode(maDocument
, mpTarget
);
442 mpTarget
->parseAttributes(xAttribs
);
445 case SVGToken::FeGaussianBlur
:
447 /// new node for feGaussianBlur
448 mpTarget
= new SvgFeGaussianBlurNode(maDocument
, mpTarget
);
449 mpTarget
->parseAttributes(xAttribs
);
452 case SVGToken::FeMerge
:
454 /// new node for feMerge
455 mpTarget
= new SvgFeMergeNode(aSVGToken
, maDocument
, mpTarget
);
456 mpTarget
->parseAttributes(xAttribs
);
459 case SVGToken::FeMergeNode
:
461 /// new node for feMergeNode
462 mpTarget
= new SvgFeMergeNodeNode(maDocument
, mpTarget
);
463 mpTarget
->parseAttributes(xAttribs
);
466 case SVGToken::FeOffset
:
468 /// new node for feOffset
469 mpTarget
= new SvgFeOffsetNode(maDocument
, mpTarget
);
470 mpTarget
->parseAttributes(xAttribs
);
473 case SVGToken::Filter
:
475 /// new node for Filter
476 mpTarget
= new SvgFilterNode(aSVGToken
, maDocument
, mpTarget
);
477 mpTarget
->parseAttributes(xAttribs
);
481 /// structural element marker
482 case SVGToken::Marker
:
484 /// new node for marker
485 mpTarget
= new SvgMarkerNode(maDocument
, mpTarget
);
486 mpTarget
->parseAttributes(xAttribs
);
490 /// structural element pattern
491 case SVGToken::Pattern
:
493 /// new node for pattern
494 mpTarget
= new SvgPatternNode(maDocument
, mpTarget
);
495 mpTarget
->parseAttributes(xAttribs
);
501 mpTarget
= new SvgNode(SVGToken::Unknown
, maDocument
, mpTarget
);
507 void SvgDocHdl::endElement( const OUString
& aName
)
515 const SVGToken
aSVGToken(StrToSVGToken(aName
, false));
516 SvgNode
* pTextNode(SVGToken::Text
== aSVGToken
? mpTarget
: nullptr);
517 SvgStyleNode
* pCssStyle(SVGToken::Style
== aSVGToken
? static_cast< SvgStyleNode
* >(mpTarget
) : nullptr);
518 SvgTitleDescNode
* pSvgTitleDescNode(SVGToken::Title
== aSVGToken
|| SVGToken::Desc
== aSVGToken
? static_cast< SvgTitleDescNode
* >(mpTarget
) : nullptr);
520 if(!mpTarget
->getParent())
522 // last element closing, save this tree
523 maDocument
.appendNode(std::unique_ptr
<SvgNode
>(mpTarget
));
526 mpTarget
= const_cast< SvgNode
* >(mpTarget
->getParent());
528 if (pSvgTitleDescNode
&& mpTarget
)
530 const OUString
& aText(pSvgTitleDescNode
->getText());
534 mpTarget
->parseAttribute(aSVGToken
, aText
);
538 if(pCssStyle
&& pCssStyle
->isTextCss())
541 if(!maCssContents
.empty())
543 // need to interpret css styles and remember them as StyleSheets
544 // #125325# Caution! the Css content may contain block comments
545 // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need
546 // to be removed first
547 const OUString
aCommentFreeSource(removeBlockComments(*(maCssContents
.end() - 1)));
549 if(aCommentFreeSource
.getLength())
551 pCssStyle
->addCssStyleSheet(aCommentFreeSource
);
554 maCssContents
.pop_back();
558 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
564 // cleanup read strings
565 // First pass: handle whitespace. This works in a way that handling a following
566 // node may append a space to a previous node; so correct line width calculation
567 // may only happen after this pass finishes
568 walkRecursive(pTextNode
, static_cast<SvgTspanNode
*>(pTextNode
), nullptr, whiteSpaceHandling
);
569 // Second pass: calculate line widths
570 walkRecursive(pTextNode
, static_cast<SvgTspanNode
*>(pTextNode
), nullptr, calcTextLineWidths
);
574 void SvgDocHdl::characters( const OUString
& aChars
)
576 const sal_uInt32
nLength(aChars
.getLength());
578 if(!(mpTarget
&& nLength
))
581 switch(mpTarget
->getType())
584 case SVGToken::Tspan
:
585 case SVGToken::TextPath
:
587 const auto& rChilds
= mpTarget
->getChildren();
591 SvgNode
* pChild
= rChilds
[rChilds
.size() - 1].get();
592 if ( pChild
->getType() == SVGToken::Character
)
594 SvgCharacterNode
& rSvgCharacterNode
= static_cast< SvgCharacterNode
& >(*pChild
);
596 // concatenate to current character span
597 rSvgCharacterNode
.concatenate(aChars
);
602 // add character span as simplified tspan (no arguments)
603 // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
604 new SvgCharacterNode(maDocument
, mpTarget
, aChars
);
607 case SVGToken::Style
:
609 SvgStyleNode
& rSvgStyleNode
= static_cast< SvgStyleNode
& >(*mpTarget
);
611 if(rSvgStyleNode
.isTextCss())
613 // collect characters for css style
614 if(!maCssContents
.empty())
616 const OUString
aTrimmedChars(aChars
.trim());
618 if(!aTrimmedChars
.isEmpty())
620 std::vector
< OUString
>::iterator
aString(maCssContents
.end() - 1);
621 (*aString
) += aTrimmedChars
;
626 OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
631 case SVGToken::Title
:
634 SvgTitleDescNode
& rSvgTitleDescNode
= static_cast< SvgTitleDescNode
& >(*mpTarget
);
636 // add text directly to SvgTitleDescNode
637 rSvgTitleDescNode
.concatenate(aChars
);
642 // characters not used by a known node
648 void SvgDocHdl::ignorableWhitespace(const OUString
& /*aWhitespaces*/)
652 void SvgDocHdl::processingInstruction(const OUString
& /*aTarget*/, const OUString
& /*aData*/)
656 void SvgDocHdl::setDocumentLocator(const uno::Reference
< xml::sax::XLocator
>& /*xLocator*/)
659 } // end of namespace svgio
661 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */