summaryrefslogtreecommitdiffstats
path: root/svgio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svgio
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svgio')
-rw-r--r--svgio/CppunitTest_svgio.mk78
-rw-r--r--svgio/CppunitTest_svgio_read.mk64
-rw-r--r--svgio/CppunitTest_svgio_tools.mk46
-rw-r--r--svgio/IwyuFilter_svgio.yaml2
-rw-r--r--svgio/Library_svgio.mk100
-rw-r--r--svgio/Makefile14
-rw-r--r--svgio/Module_svgio.mk31
-rw-r--r--svgio/README.md36
-rw-r--r--svgio/inc/SvgNumber.hxx115
-rw-r--r--svgio/inc/pch/precompiled_svgio.cxx12
-rw-r--r--svgio/inc/pch/precompiled_svgio.hxx69
-rw-r--r--svgio/inc/svganode.hxx54
-rw-r--r--svgio/inc/svgcharacternode.hxx81
-rw-r--r--svgio/inc/svgcirclenode.hxx66
-rw-r--r--svgio/inc/svgclippathnode.hxx64
-rw-r--r--svgio/inc/svgdocument.hxx85
-rw-r--r--svgio/inc/svgdocumenthandler.hxx64
-rw-r--r--svgio/inc/svgellipsenode.hxx70
-rw-r--r--svgio/inc/svgfecolormatrixnode.hxx53
-rw-r--r--svgio/inc/svgfedropshadownode.hxx47
-rw-r--r--svgio/inc/svgfefloodnode.hxx48
-rw-r--r--svgio/inc/svgfegaussianblurnode.hxx44
-rw-r--r--svgio/inc/svgfeimagenode.hxx44
-rw-r--r--svgio/inc/svgfeoffsetnode.hxx44
-rw-r--r--svgio/inc/svgfilternode.hxx39
-rw-r--r--svgio/inc/svggnode.hxx55
-rw-r--r--svgio/inc/svggradientnode.hxx118
-rw-r--r--svgio/inc/svggradientstopnode.hxx51
-rw-r--r--svgio/inc/svgimagenode.hxx77
-rw-r--r--svgio/inc/svglinenode.hxx70
-rw-r--r--svgio/inc/svgmarkernode.hxx113
-rw-r--r--svgio/inc/svgmasknode.hxx84
-rw-r--r--svgio/inc/svgnode.hxx201
-rw-r--r--svgio/inc/svgpaint.hxx52
-rw-r--r--svgio/inc/svgpathnode.hxx67
-rw-r--r--svgio/inc/svgpatternnode.hxx116
-rw-r--r--svgio/inc/svgpolynode.hxx61
-rw-r--r--svgio/inc/svgrectnode.hxx78
-rw-r--r--svgio/inc/svgstyleattributes.hxx458
-rw-r--r--svgio/inc/svgstylenode.hxx58
-rw-r--r--svgio/inc/svgsvgnode.hxx93
-rw-r--r--svgio/inc/svgsymbolnode.hxx46
-rw-r--r--svgio/inc/svgtextnode.hxx62
-rw-r--r--svgio/inc/svgtextpathnode.hxx60
-rw-r--r--svgio/inc/svgtextposition.hxx72
-rw-r--r--svgio/inc/svgtitledescnode.hxx52
-rw-r--r--svgio/inc/svgtoken.hxx200
-rw-r--r--svgio/inc/svgtools.hxx137
-rw-r--r--svgio/inc/svgtrefnode.hxx53
-rw-r--r--svgio/inc/svgtspannode.hxx92
-rw-r--r--svgio/inc/svgusenode.hxx73
-rw-r--r--svgio/inc/svgvisitor.hxx35
-rw-r--r--svgio/qa/cppunit/SvgImportTest.cxx1984
-rw-r--r--svgio/qa/cppunit/SvgNumberTest.cxx95
-rw-r--r--svgio/qa/cppunit/SvgRead.cxx127
-rw-r--r--svgio/qa/cppunit/data/47446.svg19
-rw-r--r--svgio/qa/cppunit/data/47446b.svg18
-rw-r--r--svgio/qa/cppunit/data/ClipPathAndParentStyle.svg10
-rw-r--r--svgio/qa/cppunit/data/ClipPathAndStyle.svg13
-rw-r--r--svgio/qa/cppunit/data/ClipPathUsingClipPath.svg24
-rw-r--r--svgio/qa/cppunit/data/ClipRule.svg18
-rw-r--r--svgio/qa/cppunit/data/CssClassRedefinition.svg13
-rw-r--r--svgio/qa/cppunit/data/Drawing_NoWidthHeight.svg12
-rw-r--r--svgio/qa/cppunit/data/Drawing_WithWidthHeight.svg14
-rw-r--r--svgio/qa/cppunit/data/FillRule.svg6
-rw-r--r--svgio/qa/cppunit/data/FontsizeKeywords.svg14
-rw-r--r--svgio/qa/cppunit/data/FontsizePercentage.svg3
-rw-r--r--svgio/qa/cppunit/data/FontsizeRelative.svg6
-rw-r--r--svgio/qa/cppunit/data/MarkerOrient.svg22
-rw-r--r--svgio/qa/cppunit/data/RGBAColor.svg4
-rw-r--r--svgio/qa/cppunit/data/RGBColor.svg4
-rw-r--r--svgio/qa/cppunit/data/Rect.svg4
-rw-r--r--svgio/qa/cppunit/data/RectWithParentStyles.svg13
-rw-r--r--svgio/qa/cppunit/data/RectWithStyles.svg4
-rw-r--r--svgio/qa/cppunit/data/RectWithStylesByGroup.svg18
-rw-r--r--svgio/qa/cppunit/data/ShapeWithClipPath.svg13
-rw-r--r--svgio/qa/cppunit/data/ShapeWithClipPathAndCssStyle.svg13
-rw-r--r--svgio/qa/cppunit/data/VisiotorTest-Rect.svg4
-rw-r--r--svgio/qa/cppunit/data/em_units.svg14
-rw-r--r--svgio/qa/cppunit/data/filterFeColorMatrix.svg59
-rw-r--r--svgio/qa/cppunit/data/filterFeDropShadow.svg10
-rw-r--r--svgio/qa/cppunit/data/filterFeFlood.svg16
-rw-r--r--svgio/qa/cppunit/data/filterFeGaussianBlur.svg11
-rw-r--r--svgio/qa/cppunit/data/filterFeImage.svg16
-rw-r--r--svgio/qa/cppunit/data/filterFeOffset.svg18
-rw-r--r--svgio/qa/cppunit/data/i125329.svg12
-rw-r--r--svgio/qa/cppunit/data/markerInCssStyle.svg14
-rw-r--r--svgio/qa/cppunit/data/markerInPresentation.svg12
-rw-r--r--svgio/qa/cppunit/data/maskText.svg26
-rw-r--r--svgio/qa/cppunit/data/masking-path-07-b.svg147
-rw-r--r--svgio/qa/cppunit/data/noneColor.svg3
-rw-r--r--svgio/qa/cppunit/data/path.svg3
-rw-r--r--svgio/qa/cppunit/data/symbol.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf101237.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf103888.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf104339.svg31
-rw-r--r--svgio/qa/cppunit/data/tdf117920.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf123926.svg14
-rw-r--r--svgio/qa/cppunit/data/tdf129356.svg34
-rw-r--r--svgio/qa/cppunit/data/tdf145896.svg12
-rw-r--r--svgio/qa/cppunit/data/tdf149673.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf149880.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf149893.svg3
-rw-r--r--svgio/qa/cppunit/data/tdf150124.svg12
-rw-r--r--svgio/qa/cppunit/data/tdf151103.svg18
-rw-r--r--svgio/qa/cppunit/data/tdf155733.svg20
-rw-r--r--svgio/qa/cppunit/data/tdf155814.svg25
-rw-r--r--svgio/qa/cppunit/data/tdf155819.svg15
-rw-r--r--svgio/qa/cppunit/data/tdf155833.svg31
-rw-r--r--svgio/qa/cppunit/data/tdf155932.svg22
-rw-r--r--svgio/qa/cppunit/data/tdf156018.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf156034.svg34
-rw-r--r--svgio/qa/cppunit/data/tdf156038.svg34
-rw-r--r--svgio/qa/cppunit/data/tdf156167.svg18
-rw-r--r--svgio/qa/cppunit/data/tdf156168.svg34
-rw-r--r--svgio/qa/cppunit/data/tdf156201.svg36
-rw-r--r--svgio/qa/cppunit/data/tdf156236.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf156251.svg17
-rw-r--r--svgio/qa/cppunit/data/tdf156269.svg8
-rw-r--r--svgio/qa/cppunit/data/tdf156271.svg6
-rw-r--r--svgio/qa/cppunit/data/tdf156283.svg4
-rw-r--r--svgio/qa/cppunit/data/tdf156569.svg4
-rw-r--r--svgio/qa/cppunit/data/tdf156577.svg8
-rw-r--r--svgio/qa/cppunit/data/tdf156616.svg29
-rw-r--r--svgio/qa/cppunit/data/tdf156777.svg14
-rw-r--r--svgio/qa/cppunit/data/tdf156834.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf156837.svg13
-rw-r--r--svgio/qa/cppunit/data/tdf45771.svg5
-rw-r--r--svgio/qa/cppunit/data/tdf79163.svg8
-rw-r--r--svgio/qa/cppunit/data/tdf85770.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf86938.svg13
-rw-r--r--svgio/qa/cppunit/data/tdf87309.svg4
-rw-r--r--svgio/qa/cppunit/data/tdf93583.svg7
-rw-r--r--svgio/qa/cppunit/data/tdf94765.svg14
-rw-r--r--svgio/qa/cppunit/data/tdf95400.svg8
-rw-r--r--svgio/qa/cppunit/data/tdf97542_1.svg15
-rw-r--r--svgio/qa/cppunit/data/tdf97542_2.svg15
-rw-r--r--svgio/qa/cppunit/data/tdf97543.svg4
-rw-r--r--svgio/qa/cppunit/data/tdf97710.svg8
-rw-r--r--svgio/qa/cppunit/data/tdf97717.svg11
-rw-r--r--svgio/qa/cppunit/data/tdf97936.svg5
-rw-r--r--svgio/qa/cppunit/data/tdf97941.svg6
-rw-r--r--svgio/qa/cppunit/data/tdf99115.svg40
-rw-r--r--svgio/qa/cppunit/data/tdf99994.svg8
-rw-r--r--svgio/qa/cppunit/data/textXmlSpace.svg16
-rw-r--r--svgio/qa/cppunit/data/tspan-fill-opacity.svg15
-rw-r--r--svgio/source/svgreader/SvgNumber.cxx126
-rw-r--r--svgio/source/svgreader/svganode.cxx101
-rw-r--r--svgio/source/svgreader/svgcharacternode.cxx551
-rw-r--r--svgio/source/svgreader/svgcirclenode.cxx143
-rw-r--r--svgio/source/svgreader/svgclippathnode.cxx260
-rw-r--r--svgio/source/svgreader/svgdocument.cxx94
-rw-r--r--svgio/source/svgreader/svgdocumenthandler.cxx603
-rw-r--r--svgio/source/svgreader/svgellipsenode.cxx158
-rw-r--r--svgio/source/svgreader/svgfecolormatrixnode.cxx118
-rw-r--r--svgio/source/svgreader/svgfedropshadownode.cxx146
-rw-r--r--svgio/source/svgreader/svgfefloodnode.cxx161
-rw-r--r--svgio/source/svgreader/svgfegaussianblurnode.cxx70
-rw-r--r--svgio/source/svgreader/svgfeimagenode.cxx133
-rw-r--r--svgio/source/svgreader/svgfeoffsetnode.cxx91
-rw-r--r--svgio/source/svgreader/svgfilternode.cxx56
-rw-r--r--svgio/source/svgreader/svggnode.cxx107
-rw-r--r--svgio/source/svgreader/svggradientnode.cxx501
-rw-r--r--svgio/source/svgreader/svggradientstopnode.cxx79
-rw-r--r--svgio/source/svgreader/svgimagenode.cxx348
-rw-r--r--svgio/source/svgreader/svglinenode.cxx153
-rw-r--r--svgio/source/svgreader/svgmarkernode.cxx199
-rw-r--r--svgio/source/svgreader/svgmasknode.cxx317
-rw-r--r--svgio/source/svgreader/svgnode.cxx778
-rw-r--r--svgio/source/svgreader/svgpaint.cxx22
-rw-r--r--svgio/source/svgreader/svgpathnode.cxx117
-rw-r--r--svgio/source/svgreader/svgpatternnode.cxx465
-rw-r--r--svgio/source/svgreader/svgpolynode.cxx114
-rw-r--r--svgio/source/svgreader/svgrectnode.cxx210
-rw-r--r--svgio/source/svgreader/svgstyleattributes.cxx3128
-rw-r--r--svgio/source/svgreader/svgstylenode.cxx231
-rw-r--r--svgio/source/svgreader/svgsvgnode.cxx826
-rw-r--r--svgio/source/svgreader/svgsymbolnode.cxx76
-rw-r--r--svgio/source/svgreader/svgtextnode.cxx243
-rw-r--r--svgio/source/svgreader/svgtextpathnode.cxx436
-rw-r--r--svgio/source/svgreader/svgtextposition.cxx200
-rw-r--r--svgio/source/svgreader/svgtitledescnode.cxx45
-rw-r--r--svgio/source/svgreader/svgtoken.cxx214
-rw-r--r--svgio/source/svgreader/svgtools.cxx1516
-rw-r--r--svgio/source/svgreader/svgtrefnode.cxx78
-rw-r--r--svgio/source/svgreader/svgtspannode.cxx151
-rw-r--r--svgio/source/svgreader/svgusenode.cxx180
-rw-r--r--svgio/source/svgreader/svgvisitor.cxx150
-rw-r--r--svgio/source/svguno/xsvgparser.cxx209
-rw-r--r--svgio/svgio.component26
190 files changed, 21558 insertions, 0 deletions
diff --git a/svgio/CppunitTest_svgio.mk b/svgio/CppunitTest_svgio.mk
new file mode 100644
index 0000000000..9309f5dcb9
--- /dev/null
+++ b/svgio/CppunitTest_svgio.mk
@@ -0,0 +1,78 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,svgio))
+
+$(eval $(call gb_CppunitTest_set_componentfile,svgio,svgio/svgio))
+
+$(eval $(call gb_CppunitTest_set_include,svgio,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/svgio/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,svgio,\
+ boost_headers \
+ libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,svgio))
+
+$(eval $(call gb_CppunitTest_use_library_objects,svgio,\
+ svgio \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,svgio,\
+ basegfx \
+ drawinglayer \
+ drawinglayercore \
+ cppu \
+ cppuhelper \
+ comphelper \
+ sal \
+ salhelper \
+ sax \
+ svt \
+ test \
+ unotest \
+ tk \
+ tl \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,svgio,\
+ svgio/qa/cppunit/SvgImportTest \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,svgio))
+$(eval $(call gb_CppunitTest_use_vcl,svgio))
+
+$(eval $(call gb_CppunitTest_use_components,svgio,\
+ configmgr/source/configmgr \
+ framework/util/fwk \
+ i18npool/util/i18npool \
+ package/source/xstor/xstor \
+ package/util/package2 \
+ sax/source/expatwrap/expwrap \
+ sfx2/util/sfx \
+ toolkit/util/tk \
+ ucb/source/core/ucb1 \
+ ucb/source/ucp/file/ucpfile1 \
+ unotools/util/utl \
+ vcl/vcl.common \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,svgio))
+
+# assert if font/glyph fallback occurs
+$(eval $(call gb_CppunitTest_set_non_application_font_use,svgio,abort))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,svgio))
+
+# vim: set noet sw=4 ts=4:
diff --git a/svgio/CppunitTest_svgio_read.mk b/svgio/CppunitTest_svgio_read.mk
new file mode 100644
index 0000000000..d4980dfa4b
--- /dev/null
+++ b/svgio/CppunitTest_svgio_read.mk
@@ -0,0 +1,64 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,svgio_read))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,svgio_read,\
+ svgio/qa/cppunit/SvgRead \
+))
+
+$(eval $(call gb_CppunitTest_set_include,svgio_read,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/svgio/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,svgio_read,\
+ boost_headers \
+ libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,svgio_read, \
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ drawinglayer \
+ drawinglayercore \
+ editeng \
+ i18nlangtag \
+ sal \
+ salhelper \
+ sax \
+ sot \
+ svl \
+ svt \
+ svx \
+ svxcore \
+ test \
+ tl \
+ tk \
+ ucbhelper \
+ unotest \
+ utl \
+ vcl \
+ xo \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,svgio_read))
+$(eval $(call gb_CppunitTest_use_ure,svgio_read))
+$(eval $(call gb_CppunitTest_use_vcl,svgio_read))
+$(eval $(call gb_CppunitTest_use_rdb,svgio_read,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,svgio_read,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,svgio_read))
+
+# vim: set noet sw=4 ts=4:
diff --git a/svgio/CppunitTest_svgio_tools.mk b/svgio/CppunitTest_svgio_tools.mk
new file mode 100644
index 0000000000..abb6bb6e0f
--- /dev/null
+++ b/svgio/CppunitTest_svgio_tools.mk
@@ -0,0 +1,46 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,svgio_tools))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,svgio_tools,\
+ svgio/qa/cppunit/SvgNumberTest \
+))
+
+$(eval $(call gb_CppunitTest_set_include,svgio_tools,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/svgio/inc \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,svgio_tools,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_library_objects,svgio_tools,\
+ svgio \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,svgio_tools,\
+ basegfx \
+ drawinglayer \
+ drawinglayercore \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ salhelper \
+ sax \
+ svt \
+ tk \
+ tl \
+ utl \
+ vcl \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/svgio/IwyuFilter_svgio.yaml b/svgio/IwyuFilter_svgio.yaml
new file mode 100644
index 0000000000..c8d76fb67a
--- /dev/null
+++ b/svgio/IwyuFilter_svgio.yaml
@@ -0,0 +1,2 @@
+---
+assumeFilename: svgio/source/svgreader/svgdocument.cxx
diff --git a/svgio/Library_svgio.mk b/svgio/Library_svgio.mk
new file mode 100644
index 0000000000..edd83ed572
--- /dev/null
+++ b/svgio/Library_svgio.mk
@@ -0,0 +1,100 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,svgio))
+
+$(eval $(call gb_Library_set_componentfile,svgio,svgio/svgio,services))
+
+$(eval $(call gb_Library_set_include,svgio,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/svgio/inc \
+))
+
+$(eval $(call gb_Library_use_externals,svgio,\
+ boost_headers \
+ frozen \
+))
+
+$(eval $(call gb_Library_set_precompiled_header,svgio,svgio/inc/pch/precompiled_svgio))
+
+$(eval $(call gb_Library_use_sdk_api,svgio))
+
+$(eval $(call gb_Library_use_libraries,svgio,\
+ basegfx \
+ drawinglayercore \
+ drawinglayer \
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ salhelper \
+ tk \
+ tl \
+ sax \
+ vcl \
+ svt \
+ utl \
+))
+
+$(eval $(call gb_Library_add_exception_objects,svgio,\
+ svgio/source/svgreader/svgcharacternode \
+ svgio/source/svgreader/svgcirclenode \
+ svgio/source/svgreader/svgclippathnode \
+ svgio/source/svgreader/svgdocument \
+ svgio/source/svgreader/svgdocumenthandler \
+ svgio/source/svgreader/svgellipsenode \
+ svgio/source/svgreader/svggnode \
+ svgio/source/svgreader/svganode \
+ svgio/source/svgreader/svgfecolormatrixnode \
+ svgio/source/svgreader/svgfedropshadownode \
+ svgio/source/svgreader/svgfefloodnode \
+ svgio/source/svgreader/svgfeimagenode \
+ svgio/source/svgreader/svgfegaussianblurnode \
+ svgio/source/svgreader/svgfeoffsetnode \
+ svgio/source/svgreader/svgfilternode \
+ svgio/source/svgreader/svggradientnode \
+ svgio/source/svgreader/svggradientstopnode \
+ svgio/source/svgreader/svgimagenode \
+ svgio/source/svgreader/svglinenode \
+ svgio/source/svgreader/svgmarkernode \
+ svgio/source/svgreader/svgmasknode \
+ svgio/source/svgreader/svgnode \
+ svgio/source/svgreader/SvgNumber \
+ svgio/source/svgreader/svgpaint \
+ svgio/source/svgreader/svgpathnode \
+ svgio/source/svgreader/svgpatternnode \
+ svgio/source/svgreader/svgpolynode \
+ svgio/source/svgreader/svgrectnode \
+ svgio/source/svgreader/svgstyleattributes \
+ svgio/source/svgreader/svgstylenode \
+ svgio/source/svgreader/svgsvgnode \
+ svgio/source/svgreader/svgsymbolnode \
+ svgio/source/svgreader/svgtextnode \
+ svgio/source/svgreader/svgtextposition \
+ svgio/source/svgreader/svgtitledescnode \
+ svgio/source/svgreader/svgtoken \
+ svgio/source/svgreader/svgtrefnode \
+ svgio/source/svgreader/svgtools \
+ svgio/source/svgreader/svgtextpathnode \
+ svgio/source/svgreader/svgtspannode \
+ svgio/source/svgreader/svgusenode \
+ svgio/source/svgreader/svgvisitor \
+ svgio/source/svguno/xsvgparser \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/svgio/Makefile b/svgio/Makefile
new file mode 100644
index 0000000000..0997e62848
--- /dev/null
+++ b/svgio/Makefile
@@ -0,0 +1,14 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+
+include $(module_directory)/../solenv/gbuild/partial_build.mk
+
+# vim: set noet sw=4 ts=4:
diff --git a/svgio/Module_svgio.mk b/svgio/Module_svgio.mk
new file mode 100644
index 0000000000..bb75ea9d11
--- /dev/null
+++ b/svgio/Module_svgio.mk
@@ -0,0 +1,31 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Module_Module,svgio))
+
+$(eval $(call gb_Module_add_targets,svgio,\
+ Library_svgio \
+))
+
+$(eval $(call gb_Module_add_check_targets,svgio,\
+ CppunitTest_svgio \
+ CppunitTest_svgio_read \
+ CppunitTest_svgio_tools \
+))
+
+# vim: set noet ts=4 sw=4:
diff --git a/svgio/README.md b/svgio/README.md
new file mode 100644
index 0000000000..3ab87e42d3
--- /dev/null
+++ b/svgio/README.md
@@ -0,0 +1,36 @@
+# SVG Reader
+
+## Introduction
+The **svgio** module is used to read **SVG** (Scalable Vector Graphics[1])
+files. It is an XML based format for vector graphics.
+
+This module contains `svgio/source/svgreader` which is used for embedding an
+SVG file with "Insert -> Picture -> From File".
+
+SVG is an open standard provided by the World Wide Web Consortium (W3C).
+
+[1] [Scalable Vector Graphics](https://en.wikipedia.org/wiki/Scalable\_Vector\_Graphics)
+
+## How does it work?
+`svgio` module uses sax for reading xml and turns it into `drawinglayer` primitives.
+The rendering is done via `drawinglayer` primitives. For more information, you should
+refer to [drawinglayer](../drawinglayer) documentation.
+
+## Known Bugs
+Known remaining bugs for this module are gathered here:
+
+* [Bug 88278 - [META] SVG import image filter (all modules)](https://bugs.documentfoundation.org/show\_bug.cgi?id=88278)
+
+## Dependencies
+Direct dependencies for **emfio** are [**drawinglayer**](../drawinglayer) and
+[**sax**](../sax).
+
+## Related Software
+* [librsvg](https://en.wikipedia.org/wiki/Librsvg)
+* [SVG++](http://svgpp.org/)
+
+## References
+Documentation for the SVG format is available on the W3C website:
+
+* [SVG page at W3C](https://www.w3.org/Graphics/SVG/)
+* [SVG primer](https://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html)
diff --git a/svgio/inc/SvgNumber.hxx b/svgio/inc/SvgNumber.hxx
new file mode 100644
index 0000000000..4d03335cf4
--- /dev/null
+++ b/svgio/inc/SvgNumber.hxx
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <basegfx/range/b2drange.hxx>
+#include <vector>
+
+namespace svgio::svgreader
+{
+
+enum class NumberType
+{
+ xcoordinate,
+ ycoordinate,
+ length
+};
+
+class InfoProvider
+{
+public:
+ virtual ~InfoProvider() {}
+ virtual basegfx::B2DRange getCurrentViewPort() const = 0;
+ /// return font size of node inherited from parents
+ virtual double getCurrentFontSizeInherited() const = 0;
+ /// return xheight of node inherited from parents
+ virtual double getCurrentXHeightInherited() const = 0;
+};
+
+enum class SvgUnit
+{
+ em = 0, // relative to current font size
+ ex, // relative to current x-height
+
+ px, // 'user unit'
+ pt, // points, 1/72 in
+ pc, // 1/6 in
+ cm,
+ mm,
+ in,
+
+ percent, // relative to range
+ none // for stroke-miterlimit, which has no unit
+};
+
+class SvgNumber
+{
+private:
+ double mfNumber;
+ SvgUnit meUnit;
+
+ bool mbSet : 1;
+
+public:
+ SvgNumber()
+ : mfNumber(0.0),
+ meUnit(SvgUnit::px),
+ mbSet(false)
+ {
+ }
+
+ SvgNumber(double fNum, SvgUnit aSvgUnit = SvgUnit::px, bool bSet = true)
+ : mfNumber(fNum),
+ meUnit(aSvgUnit),
+ mbSet(bSet)
+ {
+ }
+
+ double getNumber() const
+ {
+ return mfNumber;
+ }
+
+ SvgUnit getUnit() const
+ {
+ return meUnit;
+ }
+
+ bool isSet() const
+ {
+ return mbSet;
+ }
+
+ bool isPositive() const
+ {
+ return basegfx::fTools::moreOrEqual(mfNumber, 0.0);
+ }
+
+ // Only usable in cases, when the unit is not SvgUnit::percent, otherwise use method solve
+ double solveNonPercentage(const InfoProvider& rInfoProvider) const;
+
+ double solve(const InfoProvider& rInfoProvider, NumberType aNumberType = NumberType::length) const;
+};
+
+typedef std::vector<SvgNumber> SvgNumberVector;
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/pch/precompiled_svgio.cxx b/svgio/inc/pch/precompiled_svgio.cxx
new file mode 100644
index 0000000000..843dba73ff
--- /dev/null
+++ b/svgio/inc/pch/precompiled_svgio.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "precompiled_svgio.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/pch/precompiled_svgio.hxx b/svgio/inc/pch/precompiled_svgio.hxx
new file mode 100644
index 0000000000..c9e284ca20
--- /dev/null
+++ b/svgio/inc/pch/precompiled_svgio.hxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ This file has been autogenerated by update_pch.sh. It is possible to edit it
+ manually (such as when an include file has been moved/renamed/removed). All such
+ manual changes will be rewritten by the next run of update_pch.sh (which presumably
+ also fixes all possible problems, so it's usually better to use it).
+
+ Generated on 2021-04-11 19:48:19 using:
+ ./bin/update_pch svgio svgio --cutoff=8 --exclude:system --exclude:module --include:local
+
+ If after updating build fails, use the following command to locate conflicting headers:
+ ./bin/update_pch_bisect ./svgio/inc/pch/precompiled_svgio.hxx "make svgio.build" --find-conflicts
+*/
+
+#include <sal/config.h>
+#if PCH_LEVEL >= 1
+#include <memory>
+#include <ostream>
+#include <string_view>
+#include <vector>
+#endif // PCH_LEVEL >= 1
+#if PCH_LEVEL >= 2
+#include <osl/diagnose.h>
+#include <osl/endian.h>
+#include <rtl/instance.hxx>
+#include <rtl/math.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#endif // PCH_LEVEL >= 2
+#if PCH_LEVEL >= 3
+#include <basegfx/basegfxdllapi.h>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <com/sun/star/drawing/PointSequenceSequence.hpp>
+#include <drawinglayer/drawinglayerdllapi.h>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
+#include <o3tl/cow_wrapper.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <tools/fontenum.hxx>
+#include <tools/toolsdllapi.h>
+#endif // PCH_LEVEL >= 3
+#if PCH_LEVEL >= 4
+#include <svgdocument.hxx>
+#include <svgnode.hxx>
+#include <svgpaint.hxx>
+#include <svgstyleattributes.hxx>
+#endif // PCH_LEVEL >= 4
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svganode.hxx b/svgio/inc/svganode.hxx
new file mode 100644
index 0000000000..34bc551a54
--- /dev/null
+++ b/svgio/inc/svganode.hxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgANode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgANode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgANode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// transform content
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgcharacternode.hxx b/svgio/inc/svgcharacternode.hxx
new file mode 100644
index 0000000000..d81066af47
--- /dev/null
+++ b/svgio/inc/svgcharacternode.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <rtl/ref.hxx>
+
+#include <string_view>
+
+#include <drawinglayer/attribute/fontattribute.hxx>
+#include "svgtextnode.hxx"
+#include "svgtextposition.hxx"
+
+namespace drawinglayer::primitive2d { class TextSimplePortionPrimitive2D; }
+
+namespace svgio::svgreader
+ {
+ class SvgCharacterNode final : public SvgNode
+ {
+ private:
+ /// the string data
+ OUString maText;
+
+ // keep a copy of string data before space handling
+ OUString maTextBeforeSpaceHandling;
+
+ SvgTspanNode* mpParentLine;
+
+ /// local helpers
+ rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> createSimpleTextPrimitive(
+ SvgTextPosition& rSvgTextPosition,
+ const SvgStyleAttributes& rSvgStyleAttributes) const;
+ void decomposeTextWithStyle(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ SvgTextPosition& rSvgTextPosition,
+ const SvgStyleAttributes& rSvgStyleAttributes) const;
+
+ public:
+ SvgCharacterNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent,
+ OUString aText);
+ virtual ~SvgCharacterNode() override;
+
+ static drawinglayer::attribute::FontAttribute getFontAttribute(
+ const SvgStyleAttributes& rSvgStyleAttributes);
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+
+ void decomposeText(drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const;
+ void whiteSpaceHandling();
+ SvgCharacterNode* addGap(SvgCharacterNode* pPreviousCharacterNode);
+ void concatenate(std::u16string_view rText);
+
+ /// Text content
+ const OUString& getText() const { return maText; }
+
+ void setParentLine(SvgTspanNode* pParentLine) { mpParentLine = pParentLine; }
+ };
+
+} // end of namespace svgio::svgreader
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgcirclenode.hxx b/svgio/inc/svgcirclenode.hxx
new file mode 100644
index 0000000000..613bf115cf
--- /dev/null
+++ b/svgio/inc/svgcirclenode.hxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgCircleNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maCx;
+ SvgNumber maCy;
+ SvgNumber maR;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgCircleNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgCircleNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// Cx content, set if found in current context
+ const SvgNumber& getCx() const { return maCx; }
+
+ /// Cy content, set if found in current context
+ const SvgNumber& getCy() const { return maCy; }
+
+ /// R content, set if found in current context
+ const SvgNumber& getR() const { return maR; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgclippathnode.hxx b/svgio/inc/svgclippathnode.hxx
new file mode 100644
index 0000000000..8843b9e12f
--- /dev/null
+++ b/svgio/inc/svgclippathnode.hxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgClipPathNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+ SvgUnits maClipPathUnits;
+
+ public:
+ SvgClipPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgClipPathNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// apply contained clipPath to given geometry #i124852# transform may be needed
+ void apply(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const;
+
+ /// clipPathUnits content
+ SvgUnits getClipPathUnits() const { return maClipPathUnits; }
+ void setClipPathUnits(const SvgUnits aClipPathUnits) { maClipPathUnits = aClipPathUnits; }
+
+ /// transform content
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgdocument.hxx b/svgio/inc/svgdocument.hxx
new file mode 100644
index 0000000000..77b4d38911
--- /dev/null
+++ b/svgio/inc/svgdocument.hxx
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+namespace svgio::svgreader
+ {
+ typedef std::vector< std::unique_ptr<SvgNode> > SvgNodeVector;
+
+ class SvgDocument
+ {
+ private:
+ /// the document hierarchy with all root nodes
+ SvgNodeVector maNodes;
+
+ /// invalid nodes that have no parent
+ SvgNodeVector maOrphanNodes;
+
+ /// the absolute path of the Svg file in progress (if available)
+ const OUString maAbsolutePath;
+
+ /// hash mapper to find nodes by their id
+ typedef std::unordered_map< OUString, const SvgNode* > IdTokenMapper;
+ IdTokenMapper maIdTokenMapperList;
+
+ /// hash mapper to find css styles by their id
+ typedef std::unordered_map< OUString, const SvgStyleAttributes* > IdStyleTokenMapper;
+ IdStyleTokenMapper maIdStyleTokenMapperList;
+
+ public:
+ explicit SvgDocument(OUString aAbsolutePath);
+ ~SvgDocument();
+
+ SvgDocument(const SvgDocument&) = delete;
+ SvgDocument& operator=(const SvgDocument&) = delete;
+
+ /// append another root node, ownership changes
+ void appendNode(std::unique_ptr<SvgNode> pNode);
+
+ /// add/remove nodes with Id to mapper
+ void addSvgNodeToMapper(const OUString& rStr, const SvgNode& rNode);
+ void removeSvgNodeFromMapper(const OUString& rStr);
+
+ /// find a node by its Id
+ const SvgNode* findSvgNodeById(const OUString& rStr) const;
+
+ /// add/remove styles to mapper
+ void addSvgStyleAttributesToMapper(const OUString& rStr, const SvgStyleAttributes& rSvgStyleAttributes);
+
+ /// find a style by its Id
+ bool hasGlobalCssStyleAttributes() const { return !maIdStyleTokenMapperList.empty(); }
+ const SvgStyleAttributes* findGlobalCssStyleAttributes(const OUString& rStr) const;
+
+ /// data read access
+ const SvgNodeVector& getSvgNodeVector() const { return maNodes; }
+ const OUString& getAbsolutePath() const { return maAbsolutePath; }
+
+ /// invalid nodes that have no parent
+ void addOrphanNode(SvgNode* pOrphan) { maOrphanNodes.emplace_back(pOrphan); }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgdocumenthandler.hxx b/svgio/inc/svgdocumenthandler.hxx
new file mode 100644
index 0000000000..8511a0adfb
--- /dev/null
+++ b/svgio/inc/svgdocumenthandler.hxx
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include "svgdocument.hxx"
+#include <cppuhelper/implbase.hxx>
+
+class SvStream;
+namespace svgio::svgreader { class SvgCharacterNode; }
+
+namespace svgio::svgreader
+ {
+ class SvgDocHdl final : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler >
+ {
+ private:
+ // the complete SVG Document
+ SvgDocument maDocument;
+
+ // current node for parsing
+ SvgNode* mpTarget;
+
+ // text collector string stack for css styles
+ std::vector< OUString > maCssContents;
+
+ public:
+ SvgDocHdl(const OUString& rAbsolutePath);
+ virtual ~SvgDocHdl() override;
+
+ // Methods XDocumentHandler
+ virtual void SAL_CALL startDocument( ) override;
+ virtual void SAL_CALL endDocument( ) override;
+ virtual void SAL_CALL startElement( const OUString& aName, const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) override;
+ virtual void SAL_CALL endElement( const OUString& aName ) override;
+ virtual void SAL_CALL characters( const OUString& aChars ) override;
+ virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override;
+ virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override;
+ virtual void SAL_CALL setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override;
+
+ const SvgDocument& getSvgDocument() const { return maDocument; }
+ };
+
+} // end of namespace svgio::svgreader
+
+extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportSVG(SvStream& rStream);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgellipsenode.hxx b/svgio/inc/svgellipsenode.hxx
new file mode 100644
index 0000000000..1aab9bca42
--- /dev/null
+++ b/svgio/inc/svgellipsenode.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgEllipseNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maCx;
+ SvgNumber maCy;
+ SvgNumber maRx;
+ SvgNumber maRy;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgEllipseNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgEllipseNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// Cx content, set if found in current context
+ const SvgNumber& getCx() const { return maCx; }
+
+ /// Cy content, set if found in current context
+ const SvgNumber& getCy() const { return maCy; }
+
+ /// Rx content, set if found in current context
+ const SvgNumber& getRx() const { return maRx; }
+
+ /// Ry content, set if found in current context
+ const SvgNumber& getRy() const { return maRy; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfecolormatrixnode.hxx b/svgio/inc/svgfecolormatrixnode.hxx
new file mode 100644
index 0000000000..e2c74a0374
--- /dev/null
+++ b/svgio/inc/svgfecolormatrixnode.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b3dhommatrix.hxx>
+
+namespace svgio::svgreader
+{
+enum class ColorType
+{
+ HueRotate,
+ Matrix,
+ Saturate,
+ LuminanceToAlpha
+};
+
+class SvgFeColorMatrixNode final : public SvgFilterNode
+{
+private:
+ ColorType maType;
+ OUString maValuesContent;
+
+public:
+ SvgFeColorMatrixNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeColorMatrixNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfedropshadownode.hxx b/svgio/inc/svgfedropshadownode.hxx
new file mode 100644
index 0000000000..04b3190f1d
--- /dev/null
+++ b/svgio/inc/svgfedropshadownode.hxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+{
+class SvgFeDropShadowNode final : public SvgFilterNode
+{
+private:
+ SvgNumber maDx;
+ SvgNumber maDy;
+ SvgNumber maStdDeviation;
+ SvgPaint maFloodColor;
+ SvgNumber maFloodOpacity;
+
+public:
+ SvgFeDropShadowNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeDropShadowNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfefloodnode.hxx b/svgio/inc/svgfefloodnode.hxx
new file mode 100644
index 0000000000..e74794b972
--- /dev/null
+++ b/svgio/inc/svgfefloodnode.hxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+{
+class SvgFeFloodNode final : public SvgFilterNode
+{
+private:
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+ SvgPaint maFloodColor;
+ SvgNumber maFloodOpacity;
+
+public:
+ SvgFeFloodNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeFloodNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfegaussianblurnode.hxx b/svgio/inc/svgfegaussianblurnode.hxx
new file mode 100644
index 0000000000..14732a968e
--- /dev/null
+++ b/svgio/inc/svgfegaussianblurnode.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+{
+class SvgFeGaussianBlurNode final : public SvgFilterNode
+{
+private:
+ SvgNumber maStdDeviation;
+
+public:
+ SvgFeGaussianBlurNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeGaussianBlurNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfeimagenode.hxx b/svgio/inc/svgfeimagenode.hxx
new file mode 100644
index 0000000000..0b05173494
--- /dev/null
+++ b/svgio/inc/svgfeimagenode.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+{
+class SvgFeImageNode final : public SvgFilterNode
+{
+private:
+ OUString maUrl; // external link
+ OUString maData; // base64 data
+
+public:
+ SvgFeImageNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeImageNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfeoffsetnode.hxx b/svgio/inc/svgfeoffsetnode.hxx
new file mode 100644
index 0000000000..b56971dfd7
--- /dev/null
+++ b/svgio/inc/svgfeoffsetnode.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgfilternode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+{
+class SvgFeOffsetNode final : public SvgFilterNode
+{
+private:
+ SvgNumber maDx;
+ SvgNumber maDy;
+
+public:
+ SvgFeOffsetNode(SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFeOffsetNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const override;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgfilternode.hxx b/svgio/inc/svgfilternode.hxx
new file mode 100644
index 0000000000..0c87ba54b4
--- /dev/null
+++ b/svgio/inc/svgfilternode.hxx
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+{
+class SvgFilterNode : public SvgNode
+{
+public:
+ SvgFilterNode(SVGToken aType, SvgDocument& rDocument, SvgNode* pParent);
+ virtual ~SvgFilterNode() override;
+
+ virtual void apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const;
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svggnode.hxx b/svgio/inc/svggnode.hxx
new file mode 100644
index 0000000000..5f1f4f73a2
--- /dev/null
+++ b/svgio/inc/svggnode.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgGNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgGNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgGNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// transform content
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svggradientnode.hxx b/svgio/inc/svggradientnode.hxx
new file mode 100644
index 0000000000..6a7f385c00
--- /dev/null
+++ b/svgio/inc/svggradientnode.hxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include "svgtools.hxx"
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgGradientNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// linear gradient values
+ SvgNumber maX1;
+ SvgNumber maY1;
+ SvgNumber maX2;
+ SvgNumber maY2;
+
+ /// radial gradient values
+ SvgNumber maCx;
+ SvgNumber maCy;
+ SvgNumber maR;
+ SvgNumber maFx;
+ SvgNumber maFy;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgUnits maGradientUnits;
+ drawinglayer::primitive2d::SpreadMethod maSpreadMethod;
+ std::optional<basegfx::B2DHomMatrix> mpaGradientTransform;
+
+ /// link to another gradient used as style. If maXLink
+ /// is set, the node can be fetched on demand by using
+ // tryToFindLink (buffered)
+ mutable bool mbResolvingLink; // protect against infinite link recursion
+ OUString maXLink;
+ const SvgGradientNode* mpXLink;
+
+ /// link on demand
+ void tryToFindLink();
+
+ public:
+ SvgGradientNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgGradientNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// collect gradient stop entries
+ void collectGradientEntries(drawinglayer::primitive2d::SvgGradientEntryVector& aVector) const;
+
+ /// x1 content
+ SvgNumber getX1() const;
+
+ /// y1 content
+ SvgNumber getY1() const;
+
+ /// x2 content
+ SvgNumber getX2() const;
+
+ /// y2 content
+ SvgNumber getY2() const;
+
+ /// Cx content
+ SvgNumber getCx() const;
+
+ /// Cy content
+ SvgNumber getCy() const;
+
+ /// R content
+ SvgNumber getR() const;
+
+ /// Fx content
+ const SvgNumber* getFx() const;
+
+ /// Fy content
+ const SvgNumber* getFy() const;
+
+ /// gradientUnits content
+ SvgUnits getGradientUnits() const { return maGradientUnits; }
+ void setGradientUnits(const SvgUnits aGradientUnits) { maGradientUnits = aGradientUnits; }
+
+ /// SpreadMethod content
+ drawinglayer::primitive2d::SpreadMethod getSpreadMethod() const { return maSpreadMethod; }
+ void setSpreadMethod(const drawinglayer::primitive2d::SpreadMethod aSpreadMethod) { maSpreadMethod = aSpreadMethod; }
+
+ /// transform content, set if found in current context
+ std::optional<basegfx::B2DHomMatrix> getGradientTransform() const;
+ void setGradientTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaGradientTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svggradientstopnode.hxx b/svgio/inc/svggradientstopnode.hxx
new file mode 100644
index 0000000000..7b2b5e445a
--- /dev/null
+++ b/svgio/inc/svggradientstopnode.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgGradientStopNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// local attributes
+ SvgNumber maOffset;
+
+ public:
+ SvgGradientStopNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgGradientStopNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// offset content
+ const SvgNumber& getOffset() const { return maOffset; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgimagenode.hxx b/svgio/inc/svgimagenode.hxx
new file mode 100644
index 0000000000..fa53c2e9cf
--- /dev/null
+++ b/svgio/inc/svgimagenode.hxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgImageNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgAspectRatio maSvgAspectRatio;
+ std::optional<basegfx::B2DHomMatrix>
+ mpaTransform;
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+
+ OUString maXLink; // internal link
+ OUString maUrl; // external link
+
+ OUString maData; // base64 data
+
+ public:
+ SvgImageNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgImageNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+
+ /// x content, set if found in current context
+ const SvgNumber& getX() const { return maX; }
+
+ /// y content, set if found in current context
+ const SvgNumber& getY() const { return maY; }
+
+ /// width content, set if found in current context
+ const SvgNumber& getWidth() const { return maWidth; }
+
+ /// height content, set if found in current context
+ const SvgNumber& getHeight() const { return maHeight; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svglinenode.hxx b/svgio/inc/svglinenode.hxx
new file mode 100644
index 0000000000..bce06101fe
--- /dev/null
+++ b/svgio/inc/svglinenode.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgLineNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maX1;
+ SvgNumber maY1;
+ SvgNumber maX2;
+ SvgNumber maY2;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgLineNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgLineNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// X1 content, set if found in current context
+ const SvgNumber& getX1() const { return maX1; }
+
+ /// Y1 content, set if found in current context
+ const SvgNumber& getY1() const { return maY1; }
+
+ /// X2 content, set if found in current context
+ const SvgNumber& getX2() const { return maX2; }
+
+ /// Y2 content, set if found in current context
+ const SvgNumber& getY2() const { return maY2; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgmarkernode.hxx b/svgio/inc/svgmarkernode.hxx
new file mode 100644
index 0000000000..b8fa7c000e
--- /dev/null
+++ b/svgio/inc/svgmarkernode.hxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <memory>
+
+namespace svgio::svgreader
+ {
+ class SvgMarkerNode final : public SvgNode
+ {
+ public:
+ enum class MarkerUnits
+ {
+ strokeWidth,
+ userSpaceOnUse
+ };
+
+ enum class MarkerOrient
+ {
+ notset,
+ auto_start,
+ auto_start_reverse
+ };
+
+ private:
+ /// buffered decomposition
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitives;
+
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::unique_ptr<basegfx::B2DRange>
+ mpViewBox;
+ SvgAspectRatio maSvgAspectRatio;
+ SvgNumber maRefX;
+ SvgNumber maRefY;
+ MarkerUnits maMarkerUnits;
+ SvgNumber maMarkerWidth;
+ SvgNumber maMarkerHeight;
+ double mfAngle;
+ MarkerOrient maMarkerOrient;
+
+ public:
+ SvgMarkerNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgMarkerNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// get marker primitives buffered, uses decomposeSvgNode internally
+ const drawinglayer::primitive2d::Primitive2DContainer& getMarkerPrimitives() const;
+
+ /// InfoProvider support for % values
+ virtual basegfx::B2DRange getCurrentViewPort() const override;
+
+ /// viewBox content
+ const basegfx::B2DRange* getViewBox() const { return mpViewBox.get(); }
+ void setViewBox(const basegfx::B2DRange* pViewBox) { mpViewBox.reset(); if(pViewBox) mpViewBox.reset( new basegfx::B2DRange(*pViewBox) ); }
+
+ /// SvgAspectRatio content
+ const SvgAspectRatio& getSvgAspectRatio() const { return maSvgAspectRatio; }
+
+ /// RefX content, set if found in current context
+ const SvgNumber& getRefX() const { return maRefX; }
+
+ /// RefY content, set if found in current context
+ const SvgNumber& getRefY() const { return maRefY; }
+
+ /// MarkerUnits content
+ MarkerUnits getMarkerUnits() const { return maMarkerUnits; }
+ void setMarkerUnits(const MarkerUnits aMarkerUnits) { maMarkerUnits = aMarkerUnits; }
+
+ /// MarkerWidth content, set if found in current context
+ const SvgNumber& getMarkerWidth() const { return maMarkerWidth; }
+
+ /// MarkerHeight content, set if found in current context
+ const SvgNumber& getMarkerHeight() const { return maMarkerHeight; }
+
+ /// Angle content, set if found in current context
+ double getAngle() const { return mfAngle; }
+ void setAngle(double fAngle) { mfAngle = fAngle;}
+
+ /// MarkerOrient content
+ MarkerOrient getMarkerOrient() const { return maMarkerOrient; }
+ void setMarkerOrient(const MarkerOrient aMarkerOrient) { maMarkerOrient = aMarkerOrient; }
+
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgmasknode.hxx b/svgio/inc/svgmasknode.hxx
new file mode 100644
index 0000000000..386ab9229a
--- /dev/null
+++ b/svgio/inc/svgmasknode.hxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgMaskNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+ std::optional<basegfx::B2DHomMatrix>
+ mpaTransform;
+ SvgUnits maMaskUnits;
+ SvgUnits maMaskContentUnits;
+
+ public:
+ SvgMaskNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgMaskNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// apply contained clipPath to given geometry #i124852# transform may be needed
+ void apply(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const;
+
+ /// x content, set if found in current context
+ const SvgNumber& getX() const { return maX; }
+
+ /// y content, set if found in current context
+ const SvgNumber& getY() const { return maY; }
+
+ /// width content, set if found in current context
+ const SvgNumber& getWidth() const { return maWidth; }
+
+ /// height content, set if found in current context
+ const SvgNumber& getHeight() const { return maHeight; }
+
+ /// transform content
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+
+ /// MaskUnits content
+ void setMaskUnits(const SvgUnits aMaskUnits) { maMaskUnits = aMaskUnits; }
+
+ /// MaskContentUnits content
+ void setMaskContentUnits(const SvgUnits aMaskContentUnits) { maMaskContentUnits = aMaskContentUnits; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgnode.hxx b/svgio/inc/svgnode.hxx
new file mode 100644
index 0000000000..63abc4f8cb
--- /dev/null
+++ b/svgio/inc/svgnode.hxx
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "SvgNumber.hxx"
+#include "svgtoken.hxx"
+#include <com/sun/star/xml/sax/XAttributeList.hpp>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <memory>
+#include <string_view>
+#include <vector>
+#include <optional>
+
+// predefines
+namespace svgio::svgreader
+{
+ class SvgNode;
+ class SvgDocument;
+ class SvgStyleAttributes;
+}
+
+
+
+namespace svgio::svgreader
+ {
+ enum class XmlSpace
+ {
+ NotSet,
+ Default,
+ Preserve
+ };
+
+ // display property (see SVG 1.1. 11.5), not inheritable
+ enum class Display // #i121656#
+ {
+ Inline, // the default
+ Block,
+ ListItem,
+ RunIn,
+ Compact,
+ Marker,
+ Table,
+ InlineTable,
+ TableRowGroup,
+ TableHeaderGroup,
+ TableFooterGroup,
+ TableRow,
+ TableColumnGroup,
+ TableColumn,
+ TableCell,
+ TableCaption,
+ None,
+ Inherit
+ };
+
+ // helper to convert a string associated with a token of type SVGTokenDisplay
+ // to the enum Display. Empty strings return the default 'Display_inline' with
+ // which members should be initialized
+ Display getDisplayFromContent(std::u16string_view aContent);
+
+ class Visitor;
+
+ class SvgNode : public InfoProvider
+ {
+ private:
+ /// basic data, Type, document we belong to and parent (if not root)
+ SVGToken maType;
+ SvgDocument& mrDocument;
+ const SvgNode* mpParent;
+ const SvgNode* mpAlternativeParent;
+
+ /// sub hierarchy
+ std::vector< std::unique_ptr<SvgNode> > maChildren;
+
+ /// Id svan value
+ std::optional<OUString> mpId;
+
+ /// Class svan value
+ std::optional<OUString> mpClass;
+
+ /// XmlSpace value
+ XmlSpace maXmlSpace;
+
+ /// Display value #i121656#
+ Display maDisplay;
+
+ // CSS style vector chain, used in decompose phase and built up once per node.
+ // It contains the StyleHierarchy for the local node. Independent from the
+ // node hierarchy itself which also needs to be used in style entry solving
+ ::std::vector< const SvgStyleAttributes* > maCssStyleVector;
+
+ /// possible local CssStyle, e.g. style="fill:red; stroke:red;"
+ std::unique_ptr<SvgStyleAttributes> mpLocalCssStyle;
+
+ mutable bool mbDecomposing;
+
+ // flag if maCssStyleVector is already computed (done only once)
+ bool mbCssStyleVectorBuilt : 1;
+
+ protected:
+ /// helper to evtl. link to css style
+ const SvgStyleAttributes* checkForCssStyle(const SvgStyleAttributes& rOriginal) const;
+
+ /// helper for filling the CssStyle vector once dependent on mbCssStyleVectorBuilt
+ void fillCssStyleVector(const SvgStyleAttributes& rOriginal);
+ void addCssStyle(
+ const SvgDocument& rDocument,
+ const OUString& aConcatenated);
+ void fillCssStyleVectorUsingHierarchyAndSelectors(
+ const SvgNode& rCurrent,
+ std::u16string_view aConcatenated);
+ void fillCssStyleVectorUsingParent(
+ const SvgNode& rCurrent);
+
+ public:
+ SvgNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgNode() override;
+ SvgNode(const SvgNode&) = delete;
+ SvgNode& operator=(const SvgNode&) = delete;
+
+ void accept(Visitor& rVisitor);
+
+ /// scan helper to read and interpret a local CssStyle to mpLocalCssStyle
+ void readLocalCssStyle(std::u16string_view aContent);
+
+ /// style helpers
+ void parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs);
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent);
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const;
+
+ /// #i125258# tell if this node is allowed to have a parent style (e.g. defs do not)
+ virtual bool supportsParentStyle() const;
+
+ /// basic data read access
+ SVGToken getType() const { return maType; }
+ const SvgDocument& getDocument() const { return mrDocument; }
+ const SvgNode* getParent() const { if(mpAlternativeParent) return mpAlternativeParent; return mpParent; }
+ const std::vector< std::unique_ptr<SvgNode> > & getChildren() const { return maChildren; }
+
+ /// InfoProvider support for %, em and ex values
+ virtual basegfx::B2DRange getCurrentViewPort() const override;
+ virtual double getCurrentFontSizeInherited() const override;
+ virtual double getCurrentXHeightInherited() const override;
+
+ double getCurrentFontSize() const;
+ double getCurrentXHeight() const;
+
+ /// Id access
+ std::optional<OUString> const & getId() const { return mpId; }
+ void setId(OUString const &);
+
+ /// Class access
+ std::optional<OUString> const & getClass() const { return mpClass; }
+ void setClass(OUString const &);
+
+ /// XmlSpace access
+ XmlSpace getXmlSpace() const;
+ void setXmlSpace(XmlSpace eXmlSpace) { maXmlSpace = eXmlSpace; }
+
+ /// Display access #i121656#
+ Display getDisplay() const { return maDisplay; }
+ void setDisplay(Display eDisplay) { maDisplay = eDisplay; }
+
+ /// alternative parent
+ void setAlternativeParent(const SvgNode* pAlternativeParent = nullptr) { mpAlternativeParent = pAlternativeParent; }
+
+ /// Check if there is a local css style
+ bool hasLocalCssStyle() { return static_cast<bool>(mpLocalCssStyle); }
+ };
+
+ class Visitor
+ {
+ public:
+ virtual ~Visitor() = default;
+ virtual void visit(SvgNode const & pNode) = 0;
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgpaint.hxx b/svgio/inc/svgpaint.hxx
new file mode 100644
index 0000000000..ad1c232dfb
--- /dev/null
+++ b/svgio/inc/svgpaint.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <basegfx/color/bcolor.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgPaint
+ {
+ private:
+ basegfx::BColor maColor;
+
+ bool mbSet : 1;
+ bool mbOn : 1;
+ bool mbCurrent : 1;
+
+ public:
+ SvgPaint(const basegfx::BColor& rColor = basegfx::BColor(0.0, 0.0, 0.0), bool bSet = false, bool bOn = false, bool bCurrent = false)
+ : maColor(rColor),
+ mbSet(bSet),
+ mbOn(bOn),
+ mbCurrent(bCurrent)
+ {
+ }
+
+ const basegfx::BColor& getBColor() const { return maColor; }
+ bool isSet() const { return mbSet; }
+ bool isOn() const { return mbOn; }
+ bool isCurrent() const { return mbCurrent; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgpathnode.hxx b/svgio/inc/svgpathnode.hxx
new file mode 100644
index 0000000000..5f71c51cd0
--- /dev/null
+++ b/svgio/inc/svgpathnode.hxx
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <optional>
+
+namespace svgio::svgreader
+ {
+ class SvgPathNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DPolyPolygon> mpPolyPolygon;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+ SvgNumber maPathLength;
+ basegfx::utils::PointIndexSet maHelpPointIndices;
+
+ public:
+ SvgPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgPathNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// path content, set if found in current context
+ const std::optional<basegfx::B2DPolyPolygon>& getPath() const { return mpPolyPolygon; }
+ void setPath(const std::optional<basegfx::B2DPolyPolygon>& pPath) { mpPolyPolygon = pPath; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+
+ /// PathLength content
+ const SvgNumber& getPathLength() const { return maPathLength; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgpatternnode.hxx b/svgio/inc/svgpatternnode.hxx
new file mode 100644
index 0000000000..7588604080
--- /dev/null
+++ b/svgio/inc/svgpatternnode.hxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <memory>
+
+namespace svgio::svgreader
+ {
+ class SvgPatternNode final : public SvgNode
+ {
+ private:
+ /// buffered decomposition
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitives;
+
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::unique_ptr<basegfx::B2DRange>
+ mpViewBox;
+ SvgAspectRatio maSvgAspectRatio;
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+ std::optional<SvgUnits>
+ moPatternUnits;
+ std::optional<SvgUnits>
+ moPatternContentUnits;
+ std::optional<basegfx::B2DHomMatrix>
+ mpaPatternTransform;
+
+ /// link to another pattern used as style. If maXLink
+ /// is set, the node can be fetched on demand by using
+ // tryToFindLink (buffered)
+ mutable bool mbResolvingLink; // protect against infinite link recursion
+ OUString maXLink;
+ const SvgPatternNode* mpXLink;
+
+ /// link on demand
+ void tryToFindLink();
+
+ public:
+ SvgPatternNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgPatternNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// global helpers
+ void getValuesRelative(double& rfX, double& rfY, double& rfW, double& rfH, const basegfx::B2DRange& rGeoRange, SvgNode const & rUser) const;
+
+ /// get pattern primitives buffered, uses decomposeSvgNode internally
+ const drawinglayer::primitive2d::Primitive2DContainer& getPatternPrimitives() const;
+
+ /// InfoProvider support for % values
+ virtual basegfx::B2DRange getCurrentViewPort() const override;
+
+ /// viewBox content
+ const basegfx::B2DRange* getViewBox() const;
+ void setViewBox(const basegfx::B2DRange* pViewBox) { mpViewBox.reset(); if(pViewBox) mpViewBox.reset(new basegfx::B2DRange(*pViewBox)); }
+
+ /// SvgAspectRatio content
+ const SvgAspectRatio& getSvgAspectRatio() const;
+
+ /// X content, set if found in current context
+ const SvgNumber& getX() const;
+
+ /// Y content, set if found in current context
+ const SvgNumber& getY() const;
+
+ /// Width content, set if found in current context
+ const SvgNumber& getWidth() const;
+
+ /// Height content, set if found in current context
+ const SvgNumber& getHeight() const;
+
+ /// PatternUnits content
+ const SvgUnits* getPatternUnits() const;
+ void setPatternUnits(const SvgUnits aPatternUnits) { moPatternUnits = aPatternUnits; }
+
+ /// PatternContentUnits content
+ const SvgUnits* getPatternContentUnits() const;
+ void setPatternContentUnits(const SvgUnits aPatternContentUnits) { moPatternContentUnits = aPatternContentUnits; }
+
+ /// PatternTransform content
+ std::optional<basegfx::B2DHomMatrix> getPatternTransform() const;
+ void setPatternTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaPatternTransform = pMatrix; }
+
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgpolynode.hxx b/svgio/inc/svgpolynode.hxx
new file mode 100644
index 0000000000..f692d2a1de
--- /dev/null
+++ b/svgio/inc/svgpolynode.hxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <optional>
+
+namespace svgio::svgreader
+ {
+ class SvgPolyNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DPolygon> mpPolygon;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgPolyNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgPolyNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// Polygon content, set if found in current context
+ void setPolygon(const std::optional<basegfx::B2DPolygon>& pPolygon) { mpPolygon = pPolygon; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgrectnode.hxx b/svgio/inc/svgrectnode.hxx
new file mode 100644
index 0000000000..161d11eb40
--- /dev/null
+++ b/svgio/inc/svgrectnode.hxx
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgRectNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+ SvgNumber maRx;
+ SvgNumber maRy;
+ std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+ public:
+ SvgRectNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgRectNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// x content, set if found in current context
+ const SvgNumber& getX() const { return maX; }
+
+ /// y content, set if found in current context
+ const SvgNumber& getY() const { return maY; }
+
+ /// width content, set if found in current context
+ const SvgNumber& getWidth() const { return maWidth; }
+
+ /// height content, set if found in current context
+ const SvgNumber& getHeight() const { return maHeight; }
+
+ /// Rx content, set if found in current context
+ const SvgNumber& getRx() const { return maRx; }
+
+ /// Ry content, set if found in current context
+ const SvgNumber& getRy() const { return maRy; }
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgstyleattributes.hxx b/svgio/inc/svgstyleattributes.hxx
new file mode 100644
index 0000000000..6078194266
--- /dev/null
+++ b/svgio/inc/svgstyleattributes.hxx
@@ -0,0 +1,458 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgpaint.hxx"
+#include "svgnode.hxx"
+#include "svgtools.hxx"
+#include <tools/fontenum.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+
+
+// predefines
+
+namespace svgio::svgreader {
+ class SvgGradientNode;
+ class SvgPatternNode;
+ class SvgMarkerNode;
+ class SvgClipPathNode;
+ class SvgFilterNode;
+ class SvgMaskNode;
+}
+
+
+namespace svgio::svgreader
+ {
+ enum class StrokeLinecap
+ {
+ notset,
+ butt,
+ round,
+ square
+ };
+
+ enum class StrokeLinejoin
+ {
+ notset,
+ miter,
+ round,
+ bevel
+ };
+
+ enum class FontSize
+ {
+ notset,
+ xx_small,
+ x_small,
+ small,
+ smaller,
+ medium,
+ large,
+ larger,
+ x_large,
+ xx_large,
+ initial
+ };
+
+ enum class FontStretch
+ {
+ notset,
+ normal,
+ wider,
+ narrower,
+ ultra_condensed,
+ extra_condensed,
+ condensed,
+ semi_condensed,
+ semi_expanded,
+ expanded,
+ extra_expanded,
+ ultra_expanded
+ };
+
+ FontStretch getWider(FontStretch aSource);
+ FontStretch getNarrower(FontStretch aSource);
+
+ enum class FontStyle
+ {
+ notset,
+ normal,
+ italic,
+ oblique
+ };
+
+ enum class FontWeight
+ {
+ notset,
+ N100,
+ N200,
+ N300,
+ N400, // same as normal
+ N500,
+ N600,
+ N700, // same as bold
+ N800,
+ N900,
+ bolder,
+ lighter,
+ };
+
+ FontWeight getBolder(FontWeight aSource);
+ FontWeight getLighter(FontWeight aSource);
+ ::FontWeight getVclFontWeight(FontWeight aSource);
+
+ enum class TextAlign
+ {
+ notset,
+ left,
+ right,
+ center,
+ justify
+ };
+
+ enum class TextDecoration
+ {
+ notset,
+ none,
+ underline,
+ overline,
+ line_through,
+ blink
+ };
+
+ enum class TextAnchor
+ {
+ notset,
+ start,
+ middle,
+ end
+ };
+
+ enum class FillRule
+ {
+ notset,
+ nonzero,
+ evenodd
+ };
+
+ enum class BaselineShift
+ {
+ Baseline,
+ Sub,
+ Super,
+ Percentage,
+ Length
+ };
+
+ enum class DominantBaseline
+ {
+ Auto,
+ Middle,
+ Hanging
+ };
+
+ enum class Visibility
+ {
+ notset,
+ visible,
+ hidden,
+ collapse,
+ inherit
+ };
+
+ class SvgStyleAttributes
+ {
+ private:
+ SvgNode& mrOwner;
+ const SvgStyleAttributes* mpCssStyleParent;
+ SvgPaint maFill;
+ SvgPaint maStroke;
+ SvgPaint maStopColor;
+ SvgNumber maStrokeWidth;
+ SvgNumber maStopOpacity;
+ SvgNumber maFillOpacity;
+ SvgNumberVector maStrokeDasharray;
+ SvgNumber maStrokeDashOffset;
+ StrokeLinecap maStrokeLinecap;
+ StrokeLinejoin maStrokeLinejoin;
+ SvgNumber maStrokeMiterLimit;
+ SvgNumber maStrokeOpacity;
+ SvgStringVector maFontFamily;
+ FontSize maFontSize;
+ SvgNumber maFontSizeNumber;
+ FontStretch maFontStretch;
+ FontStyle maFontStyle;
+ FontWeight maFontWeight;
+ TextAlign maTextAlign;
+ TextDecoration maTextDecoration;
+ TextAnchor maTextAnchor;
+ SvgPaint maColor;
+ SvgNumber maOpacity;
+ Visibility maVisibility;
+ OUString maTitle;
+ OUString maDesc;
+
+ /// link to content. If set, the node can be fetched on demand
+ OUString maClipPathXLink;
+ OUString maFilterXLink;
+ OUString maMaskXLink;
+
+ /// link to markers. If set, the node can be fetched on demand
+ OUString maMarkerStartXLink;
+ OUString maMarkerMidXLink;
+ OUString maMarkerEndXLink;
+
+ /// fill rule
+ FillRule maFillRule;
+
+ // ClipRule setting (only valid when mbIsClipPathContent == true, default is FillRule_nonzero)
+ FillRule maClipRule;
+
+ // BaselineShift: Type and number (in case of BaselineShift_Percentage or BaselineShift_Length)
+ BaselineShift maBaselineShift;
+ SvgNumber maBaselineShiftNumber;
+
+ DominantBaseline maDominantBaseline;
+
+ mutable std::vector<sal_uInt16> maResolvingParent;
+
+ // defines if this attributes are part of a ClipPath. If yes,
+ // rough geometry will be created on decomposition by patching
+ // values for fill, stroke, strokeWidth and others
+ bool mbIsClipPathContent : 1;
+
+ // #121221# Defines if evtl. an empty array *is* set
+ bool mbStrokeDasharraySet : 1;
+
+ // tdf#94765 Check id references in gradient/pattern getters
+ OUString maNodeFillURL;
+ OUString maNodeStrokeURL;
+
+ /// internal helpers
+ void add_fillGradient(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgGradientNode& rFillGradient,
+ const basegfx::B2DRange& rGeoRange) const;
+ void add_fillPatternTransform(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgPatternNode& rFillGradient,
+ const basegfx::B2DRange& rGeoRange) const;
+ void add_fillPattern(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgPatternNode& rFillGradient,
+ const basegfx::B2DRange& rGeoRange) const;
+ void add_fill(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DRange& rGeoRange) const;
+ void add_stroke(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DRange& rGeoRange) const;
+ bool prepare_singleMarker(
+ drawinglayer::primitive2d::Primitive2DContainer& rMarkerPrimitives,
+ basegfx::B2DHomMatrix& rMarkerTransform,
+ basegfx::B2DRange& rClipRange,
+ const SvgMarkerNode& rMarker) const;
+ void add_markers(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::utils::PointIndexSet* pHelpPointIndices) const;
+
+
+ public:
+ /// local attribute scanner
+ void parseStyleAttribute(SVGToken aSVGToken, const OUString& rContent);
+
+ /// helper which does the necessary with a given path
+ void add_text(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource) const;
+ void add_path(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::utils::PointIndexSet* pHelpPointIndices) const;
+ void add_postProcess(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const;
+
+ /// helper to set mpCssStyleParent temporarily for CSS style hierarchies
+ void setCssStyleParent(const SvgStyleAttributes* pNew) { mpCssStyleParent = pNew; }
+ const SvgStyleAttributes* getCssStyleParent() const { return mpCssStyleParent; }
+
+ /// scan helpers
+ void readCssStyle(std::u16string_view rCandidate);
+ const SvgStyleAttributes* getParentStyle() const;
+
+ SvgStyleAttributes(SvgNode& rOwner);
+ ~SvgStyleAttributes();
+
+ /// fill content
+ bool isFillSet() const; // #i125258# ask if fill is a direct hard attribute (no hierarchy)
+ const basegfx::BColor* getFill() const;
+ void setFill(const SvgPaint& rFill) { maFill = rFill; }
+
+ /// stroke content
+ const basegfx::BColor* getStroke() const;
+
+ /// stop color content
+ const basegfx::BColor& getStopColor() const;
+
+ /// stroke-width content
+ SvgNumber getStrokeWidth() const;
+
+ /// stop opacity content
+ SvgNumber getStopOpacity() const;
+
+ /// access to evtl. set fill gradient
+ const SvgGradientNode* getSvgGradientNodeFill() const;
+
+ /// access to evtl. set fill pattern
+ const SvgPatternNode* getSvgPatternNodeFill() const;
+
+ /// access to evtl. set stroke gradient
+ const SvgGradientNode* getSvgGradientNodeStroke() const;
+
+ /// access to evtl. set stroke pattern
+ const SvgPatternNode* getSvgPatternNodeStroke() const;
+
+ /// fill opacity content
+ SvgNumber getFillOpacity() const;
+
+ /// fill rule content
+ FillRule getFillRule() const;
+
+ /// clip rule content
+ FillRule getClipRule() const;
+
+ /// fill StrokeDasharray content
+ const SvgNumberVector& getStrokeDasharray() const;
+
+ /// StrokeDashOffset content
+ SvgNumber getStrokeDashOffset() const;
+
+ /// StrokeLinecap content
+ StrokeLinecap getStrokeLinecap() const;
+ void setStrokeLinecap(const StrokeLinecap aStrokeLinecap) { maStrokeLinecap = aStrokeLinecap; }
+
+ /// StrokeLinejoin content
+ StrokeLinejoin getStrokeLinejoin() const;
+ void setStrokeLinejoin(const StrokeLinejoin aStrokeLinejoin) { maStrokeLinejoin = aStrokeLinejoin; }
+
+ /// StrokeMiterLimit content
+ SvgNumber getStrokeMiterLimit() const;
+
+ /// StrokeOpacity content
+ SvgNumber getStrokeOpacity() const;
+
+ /// Font content
+ const SvgStringVector& getFontFamily() const;
+
+ /// FontSize content
+ void setFontSize(const FontSize aFontSize) { maFontSize = aFontSize; }
+ SvgNumber getFontSizeNumber() const;
+
+ /// FontStretch content
+ FontStretch getFontStretch() const;
+ void setFontStretch(const FontStretch aFontStretch) { maFontStretch = aFontStretch; }
+
+ /// FontStyle content
+ FontStyle getFontStyle() const;
+ void setFontStyle(const FontStyle aFontStyle) { maFontStyle = aFontStyle; }
+
+ /// FontWeight content
+ FontWeight getFontWeight() const;
+ void setFontWeight(const FontWeight aFontWeight) { maFontWeight = aFontWeight; }
+
+ /// TextAlign content
+ TextAlign getTextAlign() const;
+ void setTextAlign(const TextAlign aTextAlign) { maTextAlign = aTextAlign; }
+
+ /// TextDecoration content
+ const SvgStyleAttributes* getTextDecorationDefiningSvgStyleAttributes() const;
+ TextDecoration getTextDecoration() const;
+ void setTextDecoration(const TextDecoration aTextDecoration) { maTextDecoration = aTextDecoration; }
+
+ /// TextAnchor content
+ TextAnchor getTextAnchor() const;
+ void setTextAnchor(const TextAnchor aTextAnchor) { maTextAnchor = aTextAnchor; }
+
+ /// Color content
+ const basegfx::BColor* getColor() const;
+
+ /// Resolve current color (defaults to black if no color is specified)
+ const basegfx::BColor* getCurrentColor() const;
+
+ /// Opacity content
+ SvgNumber getOpacity() const;
+ void setOpacity(const SvgNumber& rOpacity) { maOpacity = rOpacity; }
+
+ /// Visibility
+ Visibility getVisibility() const;
+ void setVisibility(const Visibility aVisibility) { maVisibility = aVisibility; }
+
+ // Title content
+ const OUString& getTitle() const { return maTitle; }
+
+ // Desc content
+ const OUString& getDesc() const { return maDesc; }
+
+ // ClipPathXLink content
+ OUString getClipPathXLink() const;
+ const SvgClipPathNode* accessClipPathXLink() const;
+
+ // FilterXLink content
+ OUString getFilterXLink() const;
+ const SvgFilterNode* accessFilterXLink() const;
+
+ // MaskXLink content
+ OUString getMaskXLink() const;
+ const SvgMaskNode* accessMaskXLink() const;
+
+ // MarkerStartXLink content
+ OUString getMarkerStartXLink() const;
+ const SvgMarkerNode* accessMarkerStartXLink() const;
+
+ // MarkerMidXLink content
+ OUString getMarkerMidXLink() const;
+ const SvgMarkerNode* accessMarkerMidXLink() const;
+
+ // MarkerEndXLink content
+ OUString getMarkerEndXLink() const;
+ const SvgMarkerNode* accessMarkerEndXLink() const;
+
+ // BaselineShift
+ void setBaselineShift(const BaselineShift aBaselineShift) { maBaselineShift = aBaselineShift; }
+ BaselineShift getBaselineShift() const;
+ SvgNumber getBaselineShiftNumber() const;
+
+ // DominantBaseline
+ void setDominantBaseline(const DominantBaseline aDominantBaseline) { maDominantBaseline = aDominantBaseline; }
+ DominantBaseline getDominantBaseline() const;
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgstylenode.hxx b/svgio/inc/svgstylenode.hxx
new file mode 100644
index 0000000000..cb8768e965
--- /dev/null
+++ b/svgio/inc/svgstylenode.hxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <unordered_map>
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgStyleNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ std::unordered_map< OUString, std::unique_ptr<SvgStyleAttributes> > maSvgStyleAttributes;
+
+ bool mbTextCss : 1; // true == type is 'text/css'
+
+ public:
+ SvgStyleNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+
+ /// #i125258# tell if this node is allowed to have a parent style (e.g. defs do not)
+ virtual bool supportsParentStyle() const override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// CssStyleSheet add helpers
+ void addCssStyleSheet(std::u16string_view aSelectors, const SvgStyleAttributes& rNewStyle);
+ void addCssStyleSheet(std::u16string_view aSelectors, std::u16string_view aContent);
+ void addCssStyleSheet(std::u16string_view aSelectorsAndContent);
+
+ /// textCss access
+ bool isTextCss() const { return mbTextCss; }
+ void setTextCss(bool bNew) { mbTextCss = bNew; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgsvgnode.hxx b/svgio/inc/svgsvgnode.hxx
new file mode 100644
index 0000000000..4d0b40d89e
--- /dev/null
+++ b/svgio/inc/svgsvgnode.hxx
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgstyleattributes.hxx"
+#include <memory>
+
+namespace svgio::svgreader
+ {
+ class SvgSvgNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::unique_ptr<basegfx::B2DRange>
+ mpViewBox;
+ SvgAspectRatio maSvgAspectRatio;
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+ SvgNumber maVersion;
+
+ /// #i125258# bitfield
+ bool mbStyleAttributesInitialized : 1;
+
+ // #i125258# on-demand init hard attributes when this is the outmost svg element
+ // and more (see implementation)
+ void initializeStyleAttributes();
+
+ public:
+ SvgSvgNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgSvgNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// Seeks width and height of viewport, which is current before the new viewport is set.
+ // needed for percentage unit in x, y, width or height
+ void seekReferenceWidth(double& fWidth, bool& bHasFound) const;
+ void seekReferenceHeight(double& fHeight, bool& bHasFound) const;
+
+ /// InfoProvider support for % values in children
+ // The returned 'CurrentViewPort' is the viewport as it is set by this svg element
+ // and as it is needed to resolve relative values in children
+ // The method does not check for invalid width and height
+ virtual basegfx::B2DRange getCurrentViewPort() const override;
+
+ /// viewBox content
+ const basegfx::B2DRange* getViewBox() const { return mpViewBox.get(); }
+ void setViewBox(const basegfx::B2DRange* pViewBox) { mpViewBox.reset(); if(pViewBox) mpViewBox.reset( new basegfx::B2DRange(*pViewBox) ); }
+
+ /// SvgAspectRatio content
+ const SvgAspectRatio& getSvgAspectRatio() const { return maSvgAspectRatio; }
+
+ /// x content
+ const SvgNumber& getX() const { return maX; }
+
+ /// y content
+ const SvgNumber& getY() const { return maY; }
+
+ /// width content
+ const SvgNumber& getWidth() const { return maWidth; }
+
+ /// height content
+ const SvgNumber& getHeight() const { return maHeight; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgsymbolnode.hxx b/svgio/inc/svgsymbolnode.hxx
new file mode 100644
index 0000000000..7a19b335b7
--- /dev/null
+++ b/svgio/inc/svgsymbolnode.hxx
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgSymbolNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ SvgAspectRatio maSvgAspectRatio;
+
+ public:
+ SvgSymbolNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgSymbolNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtextnode.hxx b/svgio/inc/svgtextnode.hxx
new file mode 100644
index 0000000000..c95857e38f
--- /dev/null
+++ b/svgio/inc/svgtextnode.hxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgstyleattributes.hxx"
+#include "svgtextposition.hxx"
+#include "svgtspannode.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+ {
+ class SvgTextNode final : public SvgTspanNode
+ {
+ private:
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DHomMatrix>
+ mpaTransform;
+
+ /// local helpers
+ void DecomposeChild(
+ const SvgNode& rCandidate,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ SvgTextPosition& rSvgTextPosition) const;
+ static void addTextPrimitives(
+ const SvgNode& rCandidate,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource);
+
+ public:
+ SvgTextNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgTextNode() override;
+
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// transform content, set if found in current context
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtextpathnode.hxx b/svgio/inc/svgtextpathnode.hxx
new file mode 100644
index 0000000000..585b656f64
--- /dev/null
+++ b/svgio/inc/svgtextpathnode.hxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgTextPathNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// link to path content. If maXLink
+ /// is set, the node can be fetched on demand
+ OUString maXLink;
+
+ /// variable scan values, dependent of given XAttributeList
+ SvgNumber maStartOffset;
+
+ public:
+ SvgTextPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgTextPathNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ void decomposePathNode(
+ const drawinglayer::primitive2d::Primitive2DContainer& rPathContent,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DPoint& rTextStart) const;
+ bool isValid() const;
+
+ /// StartOffset content
+ const SvgNumber& getStartOffset() const { return maStartOffset; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtextposition.hxx b/svgio/inc/svgtextposition.hxx
new file mode 100644
index 0000000000..df6adc16ab
--- /dev/null
+++ b/svgio/inc/svgtextposition.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <rtl/ref.hxx>
+
+#include <string_view>
+
+#include "svgtspannode.hxx"
+
+namespace svgio::svgreader
+{
+class SvgTextPosition
+{
+private:
+ SvgTextPosition* mpParent;
+ ::std::vector<double> maX;
+ ::std::vector<double> maY;
+ ::std::vector<double> maDx;
+ ::std::vector<double> maRotate;
+ double mfTextLength;
+
+ // absolute, current, advancing position
+ basegfx::B2DPoint maPosition;
+
+ // advancing rotation index
+ sal_uInt32 mnRotationIndex;
+
+ bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs
+ bool mbAbsoluteX : 1;
+
+public:
+ SvgTextPosition(SvgTextPosition* pParent, const SvgTspanNode& rSvgCharacterNode);
+
+ // data read access
+ const SvgTextPosition* getParent() const { return mpParent; }
+ const ::std::vector<double>& getX() const { return maX; }
+ const ::std::vector<double>& getDx() const { return maDx; }
+ double getTextLength() const { return mfTextLength; }
+ bool getLengthAdjust() const { return mbLengthAdjust; }
+ bool getAbsoluteX() const { return mbAbsoluteX; }
+
+ // get/set absolute, current, advancing position
+ const basegfx::B2DPoint& getPosition() const { return maPosition; }
+ void setPosition(const basegfx::B2DPoint& rNew) { maPosition = rNew; }
+
+ // rotation handling
+ bool isRotated() const;
+ double consumeRotation();
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtitledescnode.hxx b/svgio/inc/svgtitledescnode.hxx
new file mode 100644
index 0000000000..78394d81ec
--- /dev/null
+++ b/svgio/inc/svgtitledescnode.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "svgnode.hxx"
+
+
+namespace svgio::svgreader
+ {
+ class SvgTitleDescNode final : public SvgNode
+ {
+ private:
+ /// contained chars
+ OUString maText;
+
+ public:
+ SvgTitleDescNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgTitleDescNode() override;
+
+ /// add new chars
+ void concatenate(std::u16string_view rChars);
+
+ /// x content, set if found in current context
+ const OUString& getText() const { return maText; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtoken.hxx b/svgio/inc/svgtoken.hxx
new file mode 100644
index 0000000000..3927a27d1d
--- /dev/null
+++ b/svgio/inc/svgtoken.hxx
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace svgio::svgreader
+ {
+ // SVG token mapper with hashing
+ enum class SVGToken
+ {
+ Unknown = 0,
+
+ // diverse attribute tokens
+ Width,
+ Height,
+ ViewBox,
+ Transform,
+ Style,
+ Display, // #i121656#
+ D,
+ X,
+ Y,
+ Xmlns,
+ Version,
+ Id,
+ In,
+ Rx,
+ Ry,
+ Points,
+ Dx,
+ Dy,
+ Rotate,
+ TextLength,
+ LengthAdjust,
+ Font,
+ FontFamily,
+ FontSize,
+ FontSizeAdjust,
+ FontStretch,
+ FontStyle,
+ FontVariant,
+ FontWeight,
+ Direction,
+ LetterSpacing,
+ TextDecoration,
+ UnicodeBidi,
+ WordSpacing,
+ Character, // not in the hash, just for simple text handling in SvgCharacterNode
+ Tspan,
+ Tref,
+ TextPath,
+ StartOffset,
+ Method,
+ Spacing,
+ StdDeviation,
+ TextAlign,
+ PathLength,
+ Type,
+ Class,
+ TextAnchor,
+ XmlSpace,
+ Color,
+ ClipPathNode,
+ ClipPathProperty,
+ FeColorMatrix,
+ FeDropShadow,
+ FeFlood,
+ FeImage,
+ FeGaussianBlur,
+ FeOffset,
+ Filter,
+ FloodColor,
+ FloodOpacity,
+ Mask,
+ ClipPathUnits,
+ MaskUnits,
+ MaskContentUnits,
+ ClipRule,
+ Marker,
+ MarkerStart,
+ MarkerMid,
+ MarkerEnd,
+ RefX,
+ RefY,
+ MarkerUnits,
+ MarkerWidth,
+ MarkerHeight,
+ Orient,
+ Pattern,
+ PatternUnits,
+ PatternContentUnits,
+ PatternTransform,
+ Opacity,
+ Visibility,
+ Title,
+ Desc,
+
+ // AspectRatio and params
+ PreserveAspectRatio,
+ Defer,
+ None,
+ XMinYMin,
+ XMidYMin,
+ XMaxYMin,
+ XMinYMid,
+ XMidYMid,
+ XMaxYMid,
+ XMinYMax,
+ XMidYMax,
+ XMaxYMax,
+ Meet,
+ Slice,
+ Values,
+
+ // structural elements
+ Defs,
+ G,
+ Svg,
+ Symbol,
+ Switch,
+ Use,
+ A,
+
+ // shape elements
+ Circle,
+ Ellipse,
+ Line,
+ Path,
+ Polygon,
+ Polyline,
+ Rect,
+ Image,
+
+ // gradient elements and tokens
+ LinearGradient,
+ RadialGradient,
+ Stop,
+ Offset,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ Cx,
+ Cy,
+ Fx,
+ Fy,
+ R,
+ GradientUnits,
+ GradientTransform,
+ SpreadMethod,
+ Href,
+ XlinkHref,
+ StopColor,
+ StopOpacity,
+
+ // fill tokens
+ Fill,
+ FillOpacity,
+ FillRule,
+
+ // stroke tokens
+ Stroke,
+ StrokeDasharray,
+ StrokeDashoffset,
+ StrokeLinecap,
+ StrokeLinejoin,
+ StrokeMiterlimit,
+ StrokeOpacity,
+ StrokeWidth,
+
+ // text tokens
+ Text,
+ BaselineShift,
+ DominantBaseline
+ };
+
+ SVGToken StrToSVGToken(std::u16string_view rStr, bool bIgnoreCase);
+ OUString SVGTokenToStr(const SVGToken& rToken);
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtools.hxx b/svgio/inc/svgtools.hxx
new file mode 100644
index 0000000000..6dc882b6af
--- /dev/null
+++ b/svgio/inc/svgtools.hxx
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <rtl/ustrbuf.hxx>
+#include "svgpaint.hxx"
+#include "SvgNumber.hxx"
+
+#include <string_view>
+#include <vector>
+
+
+namespace svgio::svgreader
+ {
+ // common non-token strings
+ struct commonStrings
+ {
+ static constexpr OUString aStrUserSpaceOnUse = u"userSpaceOnUse"_ustr;
+ static constexpr OUString aStrObjectBoundingBox = u"objectBoundingBox"_ustr;
+ static constexpr OUString aStrNonzero = u"nonzero"_ustr;
+ static constexpr OUString aStrEvenOdd = u"evenodd"_ustr;
+ };
+
+ enum class SvgUnits
+ {
+ userSpaceOnUse,
+ objectBoundingBox
+ };
+
+ enum class SvgAlign
+ {
+ none,
+ xMinYMin,
+ xMidYMin,
+ xMaxYMin,
+ xMinYMid,
+ xMidYMid, // default
+ xMaxYMid,
+ xMinYMax,
+ xMidYMax,
+ xMaxYMax
+ };
+
+ class SvgAspectRatio
+ {
+ private:
+ SvgAlign maSvgAlign;
+
+ bool mbMeetOrSlice : 1; // true = meet (default), false = slice
+ bool mbSet : 1;
+
+ public:
+ SvgAspectRatio()
+ : maSvgAlign(SvgAlign::xMidYMid),
+ mbMeetOrSlice(true),
+ mbSet(false)
+ {
+ }
+
+ SvgAspectRatio(SvgAlign aSvgAlign, bool bMeetOrSlice)
+ : maSvgAlign(aSvgAlign),
+ mbMeetOrSlice(bMeetOrSlice),
+ mbSet(true)
+ {
+ }
+
+ /// data read access
+ SvgAlign getSvgAlign() const { return maSvgAlign; }
+ bool isMeetOrSlice() const { return mbMeetOrSlice; }
+ bool isSet() const { return mbSet; }
+
+ /// tooling
+ static basegfx::B2DHomMatrix createLinearMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource);
+ basegfx::B2DHomMatrix createMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource) const;
+ };
+
+ void skip_char(std::u16string_view rCandidate, sal_Unicode aChar, sal_Int32& nPos, const sal_Int32 nLen);
+ void skip_char(std::u16string_view rCandidate, sal_Unicode aCharA, sal_Unicode nCharB, sal_Int32& nPos, const sal_Int32 nLen);
+ void copySign(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen);
+ void copyNumber(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen);
+ void copyHex(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen);
+ void copyString(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen);
+ void copyToLimiter(std::u16string_view rCandidate, sal_Unicode aLimiter, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen);
+ bool readNumber(std::u16string_view rCandidate, sal_Int32& nPos, double& fNum, const sal_Int32 nLen);
+ SvgUnit readUnit(std::u16string_view rCandidate, sal_Int32& nPos, const sal_Int32 nLen);
+ bool readNumberAndUnit(std::u16string_view rCandidate, sal_Int32& nPos, SvgNumber& aNum, const sal_Int32 nLen);
+ bool readAngle(std::u16string_view rCandidate, sal_Int32& nPos, double& fAngle, const sal_Int32 nLen);
+ sal_Int32 read_hex(sal_Unicode aChar);
+ bool match_colorKeyword(basegfx::BColor& rColor, const OUString& rName);
+ bool read_color(const OUString& rCandidate, basegfx::BColor& rColor, SvgNumber& rOpacity);
+ basegfx::B2DRange readViewBox(std::u16string_view rCandidate, InfoProvider const & rInfoProvider);
+ std::vector<double> readFilterMatrix(std::u16string_view rCandidate, InfoProvider const & rInfoProvider);
+ basegfx::B2DHomMatrix readTransform(std::u16string_view rCandidate, InfoProvider const & rInfoProvider);
+ bool readSingleNumber(std::u16string_view rCandidate, SvgNumber& aNum);
+ bool readLocalLink(std::u16string_view rCandidate, OUString& rURL);
+ bool readLocalUrl(const OUString& rCandidate, OUString& rURL);
+ bool readSvgPaint(const OUString& rCandidate, SvgPaint& rSvgPaint, OUString& rURL, SvgNumber& rOpacity);
+
+ bool readSvgNumberVector(std::u16string_view rCandidate, SvgNumberVector& rSvgNumberVector);
+ ::std::vector< double > solveSvgNumberVector(const SvgNumberVector& rInput, const InfoProvider& rInfoProvider);
+
+ SvgAspectRatio readSvgAspectRatio(std::u16string_view rCandidate);
+
+ typedef ::std::vector< OUString > SvgStringVector;
+ bool readSvgStringVector(std::u16string_view rCandidate, SvgStringVector& rSvgStringVector);
+
+ void readImageLink(const OUString& rCandidate, OUString& rXLink, OUString& rUrl, OUString& rData);
+
+ OUString consolidateContiguousSpace(const OUString& rCandidate);
+
+ // #125325# removes block comment of the general form '/* ... */', returns
+ // an adapted string or the original if no comments included
+ OUString removeBlockComments(const OUString& rCandidate);
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtrefnode.hxx b/svgio/inc/svgtrefnode.hxx
new file mode 100644
index 0000000000..c4701ceec4
--- /dev/null
+++ b/svgio/inc/svgtrefnode.hxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include "svgtextnode.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgTrefNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// link to text content. If maXLink
+ /// is set, the node can be fetched on demand
+ OUString maXLink;
+
+ public:
+ SvgTrefNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgTrefNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ /// access to referenced SvgTextNode
+ const SvgTextNode* getReferencedSvgTextNode() const;
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtspannode.hxx b/svgio/inc/svgtspannode.hxx
new file mode 100644
index 0000000000..84033685d8
--- /dev/null
+++ b/svgio/inc/svgtspannode.hxx
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+
+namespace svgio::svgreader
+ {
+ class SvgTspanNode : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ SvgNumberVector maX;
+ SvgNumberVector maY;
+ SvgNumberVector maDx;
+ SvgNumberVector maDy;
+ SvgNumberVector maRotate;
+ SvgNumber maTextLength;
+
+ bool mbLengthAdjust : 1; // true = spacing, false = spacingAndGlyphs
+
+ // The text line width composed by the different SvgCharacterNode children
+ // it will be used to calculate their alignment
+ double mnTextLineWidth;
+
+ public:
+ SvgTspanNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgTspanNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+
+ double getCurrentFontSize() const;
+
+ /// X content
+ const SvgNumberVector& getX() const { return maX; }
+ void setX(SvgNumberVector&& aX) { maX = std::move(aX); }
+
+ /// Y content
+ const SvgNumberVector& getY() const { return maY; }
+ void setY(SvgNumberVector&& aY) { maY = std::move(aY); }
+
+ /// Dx content
+ const SvgNumberVector& getDx() const { return maDx; }
+ void setDx(SvgNumberVector&& aDx) { maDx = std::move(aDx); }
+
+ /// Dy content
+ const SvgNumberVector& getDy() const { return maDy; }
+ void setDy(SvgNumberVector&& aDy) { maDy = std::move(aDy); }
+
+ /// Rotate content
+ const SvgNumberVector& getRotate() const { return maRotate; }
+ void setRotate(SvgNumberVector&& aRotate) { maRotate = std::move(aRotate); }
+
+ /// TextLength content
+ const SvgNumber& getTextLength() const { return maTextLength; }
+ void setTextLength(const SvgNumber& rTextLength) { maTextLength = rTextLength; }
+
+ /// LengthAdjust content
+ bool getLengthAdjust() const { return mbLengthAdjust; }
+ void setLengthAdjust(bool bNew) { mbLengthAdjust = bNew; }
+
+ void concatenateTextLineWidth(double nWidth) {mnTextLineWidth += nWidth;}
+ double getTextLineWidth() const { return mnTextLineWidth; }
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgusenode.hxx b/svgio/inc/svgusenode.hxx
new file mode 100644
index 0000000000..daf6885565
--- /dev/null
+++ b/svgio/inc/svgusenode.hxx
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "svgnode.hxx"
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <optional>
+
+namespace svgio::svgreader
+ {
+ class SvgUseNode final : public SvgNode
+ {
+ private:
+ /// use styles
+ SvgStyleAttributes maSvgStyleAttributes;
+
+ /// variable scan values, dependent of given XAttributeList
+ std::optional<basegfx::B2DHomMatrix>
+ mpaTransform;
+ SvgNumber maX;
+ SvgNumber maY;
+ SvgNumber maWidth;
+ SvgNumber maHeight;
+
+ /// link to content. If maXLink is set, the node can be fetched
+ // on demand
+ OUString maXLink;
+ /// detect if maXLink causes a loop to ourself during decomposing
+ mutable bool mbDecomposingSvgNode;
+
+ public:
+ SvgUseNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent);
+ virtual ~SvgUseNode() override;
+
+ virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+ virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) override;
+ virtual void decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const override;
+
+ /// transform content
+ const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return mpaTransform; }
+ void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix) { mpaTransform = pMatrix; }
+
+ /// x content
+ const SvgNumber& getX() const { return maX; }
+
+ /// y content
+ const SvgNumber& getY() const { return maY; }
+
+ };
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgvisitor.hxx b/svgio/inc/svgvisitor.hxx
new file mode 100644
index 0000000000..2aa1f978fe
--- /dev/null
+++ b/svgio/inc/svgvisitor.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#pragma once
+
+#include <basegfx/DrawCommands.hxx>
+#include <memory>
+#include "svgnode.hxx"
+
+namespace svgio::svgreader
+{
+class SvgDrawVisitor final : public Visitor
+{
+private:
+ std::shared_ptr<gfx::DrawRoot> mpDrawRoot;
+ std::shared_ptr<gfx::DrawBase> mpCurrent;
+
+public:
+ SvgDrawVisitor();
+
+ void visit(svgio::svgreader::SvgNode const& rNode) override;
+ void goToChildren(svgio::svgreader::SvgNode const& rNode);
+
+ std::shared_ptr<gfx::DrawRoot> const& getDrawRoot() const { return mpDrawRoot; }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx
new file mode 100644
index 0000000000..1b0be44177
--- /dev/null
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -0,0 +1,1984 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <test/bootstrapfixture.hxx>
+#include <test/xmltesttools.hxx>
+
+#include <comphelper/seqstream.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/XPrimitive2D.hpp>
+
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+
+#include <memory>
+#include <string_view>
+
+using namespace css;
+using namespace css::uno;
+using namespace css::io;
+using namespace css::graphic;
+using drawinglayer::primitive2d::Primitive2DSequence;
+using drawinglayer::primitive2d::Primitive2DContainer;
+using drawinglayer::primitive2d::Primitive2DReference;
+
+class Test : public test::BootstrapFixture, public XmlTestTools
+{
+protected:
+ void checkRectPrimitive(Primitive2DSequence const & rPrimitive);
+
+ Primitive2DSequence parseSvg(std::u16string_view aSource);
+};
+
+Primitive2DSequence Test::parseSvg(std::u16string_view aSource)
+{
+ const Reference<XSvgParser> xSvgParser = SvgTools::create(m_xContext);
+
+ OUString aUrl = m_directories.getURLFromSrc(aSource);
+ OUString aPath = m_directories.getPathFromSrc(aSource);
+
+ SvFileStream aFileStream(aUrl, StreamMode::READ);
+ std::size_t nSize = aFileStream.remainingSize();
+ std::unique_ptr<sal_Int8[]> pBuffer(new sal_Int8[nSize + 1]);
+ aFileStream.ReadBytes(pBuffer.get(), nSize);
+ pBuffer[nSize] = 0;
+
+ Sequence<sal_Int8> aData(pBuffer.get(), nSize + 1);
+ Reference<XInputStream> aInputStream(new comphelper::SequenceInputStream(aData));
+
+ return xSvgParser->getDecomposition(aInputStream, aPath);
+}
+
+void Test::checkRectPrimitive(Primitive2DSequence const & rPrimitive)
+{
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(rPrimitive));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#00cc00"); // rect background color
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "100"); // rect background height
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "100"); // rect background width
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxx"_ostr, "110");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxy"_ostr, "110");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, "color"_ostr, "#ff0000"); // rect stroke color
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, "width"_ostr, "3"); // rect stroke width
+
+
+}
+
+namespace
+{
+bool arePrimitive2DSequencesEqual(const Primitive2DSequence& rA, const Primitive2DSequence& rB)
+{
+ return std::equal(rA.begin(), rA.end(), rB.begin(), rB.end(),
+ [](const css::uno::Reference<css::graphic::XPrimitive2D>& a,
+ const css::uno::Reference<css::graphic::XPrimitive2D>& b)
+ {
+ return drawinglayer::primitive2d::arePrimitive2DReferencesEqual(a, b);
+ });
+}
+}
+
+// Attributes for an object (like rect as in this case) can be defined
+// in different ways (directly with xml attributes, or with CSS styles),
+// however the end result should be the same.
+CPPUNIT_TEST_FIXTURE(Test, testStyles)
+{
+ Primitive2DSequence aSequenceRect = parseSvg(u"/svgio/qa/cppunit/data/Rect.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRect.getLength()));
+ checkRectPrimitive(aSequenceRect);
+
+ Primitive2DSequence aSequenceRectWithStyle = parseSvg(u"/svgio/qa/cppunit/data/RectWithStyles.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRectWithStyle.getLength()));
+ checkRectPrimitive(aSequenceRectWithStyle);
+
+ Primitive2DSequence aSequenceRectWithParentStyle = parseSvg(u"/svgio/qa/cppunit/data/RectWithParentStyles.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRectWithParentStyle.getLength()));
+ checkRectPrimitive(aSequenceRectWithParentStyle);
+
+ Primitive2DSequence aSequenceRectWithStylesByGroup = parseSvg(u"/svgio/qa/cppunit/data/RectWithStylesByGroup.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRectWithStylesByGroup.getLength()));
+ checkRectPrimitive(aSequenceRectWithStylesByGroup);
+
+ CPPUNIT_ASSERT(arePrimitive2DSequencesEqual(aSequenceRect, aSequenceRectWithStyle));
+ CPPUNIT_ASSERT(arePrimitive2DSequencesEqual(aSequenceRect, aSequenceRectWithParentStyle));
+ CPPUNIT_ASSERT(arePrimitive2DSequencesEqual(aSequenceRect, aSequenceRectWithStylesByGroup));
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testSymbol)
+{
+ Primitive2DSequence aSequenceTdf87309 = parseSvg(u"/svgio/qa/cppunit/data/symbol.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf87309.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf87309);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // tdf#126330: Without the fix in place, this test would have failed with
+ // - Expected: 1
+ // - Actual : 2
+ // number of nodes is incorrect
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#00d000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf150124)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf150124.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPathChildren(pDocument, "/primitive2D"_ostr, 1);
+ assertXPath(pDocument, "/primitive2D/hiddengeometry"_ostr, 1);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf155819)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf155819.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, 1);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/polypolygon"_ostr, 1);
+ // Without the fix in place, this test would have failed with
+ // - Expected: 4
+ // - Actual : 0
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, 4);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFeColorMatrix)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/filterFeColorMatrix.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/modifiedColor[1]"_ostr, "modifier"_ostr, "matrix");
+ assertXPath(pDocument, "/primitive2D/transform/mask/modifiedColor[2]"_ostr, "modifier"_ostr, "saturate");
+ assertXPath(pDocument, "/primitive2D/transform/mask/modifiedColor[3]"_ostr, "modifier"_ostr, "hueRotate");
+ assertXPath(pDocument, "/primitive2D/transform/mask/modifiedColor[4]"_ostr, "modifier"_ostr, "luminance_to_alpha");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFilterFeGaussianBlur)
+{
+ Primitive2DSequence aSequenceTdf132246 = parseSvg(u"/svgio/qa/cppunit/data/filterFeGaussianBlur.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf132246.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf132246);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/softedge"_ostr, "radius"_ostr, "5");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFilterFeOffset)
+{
+ Primitive2DSequence aSequenceTdf132246 = parseSvg(u"/svgio/qa/cppunit/data/filterFeOffset.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf132246.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf132246);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy11"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy12"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy13"_ostr, "44");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy21"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy22"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy23"_ostr, "66");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy31"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy32"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform"_ostr, "xy33"_ostr, "1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFilterFeFlood)
+{
+ Primitive2DSequence aSequenceTdf132246 = parseSvg(u"/svgio/qa/cppunit/data/filterFeFlood.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf132246.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf132246);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "50");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFilterFeDropShadow)
+{
+ Primitive2DSequence aSequenceTdf132246 = parseSvg(u"/svgio/qa/cppunit/data/filterFeDropShadow.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf132246.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf132246);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/shadow"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/shadow"_ostr, "blur"_ostr, "0.2");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/shadow"_ostr, "blur"_ostr, "0.2");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#ffc0cb");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFilterFeImage)
+{
+ Primitive2DSequence aSequenceTdf132246 = parseSvg(u"/svgio/qa/cppunit/data/filterFeImage.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf132246.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf132246);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/bitmap"_ostr);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf87309)
+{
+ Primitive2DSequence aSequenceTdf87309 = parseSvg(u"/svgio/qa/cppunit/data/tdf87309.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf87309.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf87309);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxx"_ostr, "110");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxy"_ostr, "110");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFontsizeKeywords)
+{
+ Primitive2DSequence aSequenceFontsizeKeywords = parseSvg(u"/svgio/qa/cppunit/data/FontsizeKeywords.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceFontsizeKeywords.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceFontsizeKeywords);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "9");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "fontcolor"_ostr, "#ffffff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "11");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "fontcolor"_ostr, "#ffd700");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "height"_ostr, "13");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "fontcolor"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "fontcolor"_ostr, "#ffff00");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "height"_ostr, "19");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "fontcolor"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "height"_ostr, "23");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "fontcolor"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "height"_ostr, "27");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "fontcolor"_ostr, "#ff7f50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "height"_ostr, "13");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "fontcolor"_ostr, "#ffc0cb");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "height"_ostr, "19");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "familyname"_ostr, "Times New Roman");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "fontcolor"_ostr, "#fffff0");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+
+CPPUNIT_TEST_FIXTURE(Test, testFontsizePercentage)
+{
+ //Check when font-size uses percentage and defined globally
+ Primitive2DSequence aSequenceFontsizePercentage = parseSvg(u"/svgio/qa/cppunit/data/FontsizePercentage.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceFontsizePercentage.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceFontsizePercentage);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFontsizeRelative)
+{
+ //Check when font-size uses relative units (em,ex) and it's based on its parent's font-size
+ Primitive2DSequence aSequenceFontsizeRelative = parseSvg(u"/svgio/qa/cppunit/data/FontsizeRelative.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceFontsizeRelative.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceFontsizeRelative);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "DejaVu Serif");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "familyname"_ostr, "DejaVu Serif");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf145896)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf145896.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #ffff00
+ // - Actual : #000000
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#ffff00");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#0000ff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156168)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156168.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, 8);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[4]"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[5]"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[6]"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[7]"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[8]"_ostr, "color"_ostr, "#ff0000");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 4
+ // - Actual : 3
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke"_ostr, 4);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[1]/line"_ostr, "width"_ostr, "5");
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[1]/line"_ostr, "color"_ostr, "#00ff00");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[2]/line"_ostr, "width"_ostr, "5");
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[2]/line"_ostr, "color"_ostr, "#00ff00");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[3]/line"_ostr, "width"_ostr, "5");
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[3]/line"_ostr, "color"_ostr, "#00ff00");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[4]/line"_ostr, "width"_ostr, "5");
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke[4]/line"_ostr, "color"_ostr, "#00ff00");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf129356)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf129356.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #0000ff
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[4]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[5]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[6]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[7]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[8]"_ostr, "color"_ostr, "#008000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156034)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156034.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #0000ff
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[4]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[5]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[6]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[7]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[8]"_ostr, "color"_ostr, "#008000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156038)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156038.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#0000ff");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #0000ff
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[4]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[5]"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[6]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[7]"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[8]"_ostr, "color"_ostr, "#008000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156018)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156018.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #0000ff
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#0000ff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156201)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156201.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#2f3ba1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156167)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156167.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr, "color"_ostr, "#ffa500");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #ffa500
+ // - Actual : #ff0000
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr, "color"_ostr, "#ffa500");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]"_ostr, "color"_ostr, "#ffa500");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf155932)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf155932.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/mask/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/mask/mask/unifiedtransparence[1]/polypolygoncolor"_ostr, "color"_ostr, "#0000ff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97717)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf97717.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence[1]"_ostr, "transparence"_ostr, "50");
+ // Without the fix in place, this test would have failed here since the patch
+ // would have contained two unifiedtransparence
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence[1]/polypolygoncolor"_ostr, "color"_ostr, "#ccccff");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence[2]"_ostr, "transparence"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence[2]/polypolygoncolor"_ostr, "color"_ostr, "#ccccff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testMarkerOrient)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/MarkerOrient.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy11"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy12"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy13"_ostr, "7");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy21"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy22"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy23"_ostr, "13");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy31"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy32"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]"_ostr, "xy33"_ostr, "1");
+
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy11"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy12"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy13"_ostr, "87");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy21"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy22"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy23"_ostr, "87");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy31"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy32"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform[2]"_ostr, "xy33"_ostr, "1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testMarkerInPresentation)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/markerInPresentation.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, 1);
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/polypolygon/polygon"_ostr, 1);
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/polypolygon/polygon"_ostr, 1);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 0
+ // - Actual : 2
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform"_ostr, 0);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testMarkerInCssStyle)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/markerInCssStyle.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 20
+ // - Actual : 0
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, 20);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]/polypolygonstroke/line"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]/polypolygonstroke/line"_ostr, "width"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]/polypolygonstroke/line"_ostr, "linejoin"_ostr, "Miter");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]/polypolygonstroke/line"_ostr, "miterangle"_ostr, "28");
+ assertXPath(pDocument, "/primitive2D/transform/transform[1]/polypolygonstroke/line"_ostr, "linecap"_ostr, "BUTT");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTextXmlSpace)
+{
+ //Check tspan fontsize when using relative units
+ Primitive2DSequence aSequenceTdf97941 = parseSvg(u"/svgio/qa/cppunit/data/textXmlSpace.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97941.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf97941);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "text"_ostr, "a b");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "text"_ostr, "a b");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "text"_ostr, "a b");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "text"_ostr, "ab");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[5]"_ostr, "text"_ostr, " a b ");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[6]"_ostr, "text"_ostr, "a b");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[7]"_ostr, "text"_ostr, "a b");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[8]"_ostr, "text"_ostr, "a b");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf45771)
+{
+ //Check text fontsize when using relative units
+ Primitive2DSequence aSequenceTdf45771 = parseSvg(u"/svgio/qa/cppunit/data/tdf45771.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf45771.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf45771);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "32");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf155833)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf155833.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/transform/transform/transform/transform/transform/bitmap"_ostr, 1);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97941)
+{
+ //Check tspan fontsize when using relative units
+ Primitive2DSequence aSequenceTdf97941 = parseSvg(u"/svgio/qa/cppunit/data/tdf97941.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97941.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf97941);
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Sample");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "48");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156777)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156777.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion"_ostr, 23);
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Quick brown fox jumps over the lazy dog.");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #000000
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "84");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "23");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156834)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156834.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion"_ostr, 3);
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Auto");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "20");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "Middle");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "56");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "Hanging");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, "94");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf104339)
+{
+ Primitive2DSequence aSequenceTdf104339 = parseSvg(u"/svgio/qa/cppunit/data/tdf104339.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf104339.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequenceTdf104339);
+
+ CPPUNIT_ASSERT (pDocument);
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#000000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf85770)
+{
+ Primitive2DSequence aSequenceTdf85770 = parseSvg(u"/svgio/qa/cppunit/data/tdf85770.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf85770.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf85770));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Start Middle End");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "11");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Times New Roman");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "Start");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "11");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "familyname"_ostr, "Times New Roman");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "fontcolor"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, " End");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "height"_ostr, "11");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf86938)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf86938.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "290");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "183");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "above");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "290");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 159
+ // - Actual : 207
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "159");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "below");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "x"_ostr, "290");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, "207");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf93583)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf93583.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "This is the");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "62");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "303");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, " first");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "127");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "303");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "width"_ostr, "32");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "32");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, " line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "x"_ostr, "187");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, "303");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "height"_ostr, "16");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156616)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156616.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "First");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "114");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "103");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, " line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "142");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "103");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "Second line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "x"_ostr, "114");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, "122");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "text"_ostr, "First");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "x"_ostr, "86");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "y"_ostr, "153");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "text"_ostr, " line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "x"_ostr, "114");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "y"_ostr, "153");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "text"_ostr, "Second line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "x"_ostr, "77");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "y"_ostr, "172");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "text"_ostr, "First");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "x"_ostr, "59");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "y"_ostr, "203");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "text"_ostr, " line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "x"_ostr, "87");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "y"_ostr, "203");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "text"_ostr, "Second line");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "x"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "y"_ostr, "222");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf79163)
+{
+ //Check Opacity
+ Primitive2DSequence aSequenceTdf79163 = parseSvg(u"/svgio/qa/cppunit/data/tdf79163.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf79163.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf79163));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97542_1)
+{
+ Primitive2DSequence aSequenceTdf97542_1 = parseSvg(u"/svgio/qa/cppunit/data/tdf97542_1.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97542_1.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf97542_1));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/textsimpleportion"_ostr, "fontcolor"_ostr, "#ffff00");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/textsimpleportion"_ostr, "text"_ostr, "Text");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/textsimpleportion"_ostr, "height"_ostr, "48");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/textsimpleportion"_ostr, "familyname"_ostr, "DejaVu Serif");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97542_2)
+{
+ Primitive2DSequence aSequenceTdf97542_2 = parseSvg(u"/svgio/qa/cppunit/data/tdf97542_2.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97542_2.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf97542_2));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient"_ostr, "startx"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient"_ostr, "starty"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient/focalx"_ostr, 0);
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient/focaly"_ostr, 0);
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient"_ostr, "radius"_ostr, "3");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient"_ostr, "spreadmethod"_ostr, "pad");
+ assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient"_ostr, "opacity"_ostr, "1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97543)
+{
+ // check visibility="inherit"
+ Primitive2DSequence aSequenceTdf97543 = parseSvg(u"/svgio/qa/cppunit/data/tdf97543.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97543.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf97543));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#00cc00");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxx"_ostr, "110");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxy"_ostr, "110");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testRGBColor)
+{
+ Primitive2DSequence aSequenceRGBColor = parseSvg(u"/svgio/qa/cppunit/data/RGBColor.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRGBColor.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceRGBColor));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#646464");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxx"_ostr, "110");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon"_ostr, "maxy"_ostr, "110");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf149673)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf149673.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence"_ostr, "transparence"_ostr, "90");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor[1]"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor[2]"_ostr, "color"_ostr, "#00ff00");
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence/polypolygoncolor[3]"_ostr, "color"_ostr, "#0000ff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testRGBAColor)
+{
+ Primitive2DSequence aSequenceRGBAColor = parseSvg(u"/svgio/qa/cppunit/data/RGBAColor.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRGBAColor.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceRGBAColor));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testNoneColor)
+{
+ Primitive2DSequence aSequenceRGBAColor = parseSvg(u"/svgio/qa/cppunit/data/noneColor.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceRGBAColor.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceRGBAColor));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ //No polypolygoncolor exists
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor"_ostr, 0);
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygonstroke/line"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygonstroke/line"_ostr, "width"_ostr, "3");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf117920)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf117920.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy11"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy12"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy13"_ostr, "-18");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy21"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy22"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy23"_ostr, "-6");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy31"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy32"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform"_ostr, "xy33"_ostr, "1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97936)
+{
+ // check that both rectangles are rendered in the viewBox
+ Primitive2DSequence aSequenceTdf97936 = parseSvg(u"/svgio/qa/cppunit/data/tdf97936.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf97936.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf97936));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]"_ostr);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "height"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "width"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "minx"_ostr, "70");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "miny"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "maxx"_ostr, "120");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "maxy"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]"_ostr);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "height"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "width"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "minx"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "miny"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "maxx"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "maxy"_ostr, "100");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf149893)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf149893.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: #008000
+ // - Actual : #000000
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#008000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testShapeWithClipPathAndCssStyle)
+{
+ // tdf#97539: Check there is a mask and 3 polygons
+ Primitive2DSequence aSequenceClipPathAndStyle = parseSvg(u"/svgio/qa/cppunit/data/ShapeWithClipPathAndCssStyle.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceClipPathAndStyle.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceClipPathAndStyle));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygon/polygon"_ostr, 2);
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor/polypolygon/polygon"_ostr, 1);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testClipPathAndParentStyle)
+{
+ //Check that fill color, stroke color and stroke-width are inherited from use element
+ //when the element is within a clipPath element
+ Primitive2DSequence aSequenceClipPathAndParentStyle = parseSvg(u"/svgio/qa/cppunit/data/ClipPathAndParentStyle.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceClipPathAndParentStyle.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceClipPathAndParentStyle));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, "width"_ostr, "5");
+
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf155814)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf155814.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/mask/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/mask/mask/transform/unifiedtransparence/polypolygoncolor"_ostr, "color"_ostr, "#0000ff");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testClipPathAndStyle)
+{
+ //Check that fill color, stroke color and stroke-width are inherited from use element
+ //when the element is within a clipPath element
+ Primitive2DSequence aSequenceClipPathAndStyle = parseSvg(u"/svgio/qa/cppunit/data/ClipPathAndStyle.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceClipPathAndStyle.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceClipPathAndStyle));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#ccccff");
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, "color"_ostr, "#0000cc");
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygonstroke/line"_ostr, "width"_ostr, "2");
+
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testShapeWithClipPath)
+{
+ // Check there is a mask and 3 polygons
+ Primitive2DSequence aSequenceClipPathAndStyle = parseSvg(u"/svgio/qa/cppunit/data/ShapeWithClipPath.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceClipPathAndStyle.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceClipPathAndStyle));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygon/polygon"_ostr, 2);
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor/polypolygon/polygon"_ostr, 1);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testClipPathUsingClipPath)
+{
+ Primitive2DSequence aSequenceClipPathAndStyle = parseSvg(u"/svgio/qa/cppunit/data/ClipPathUsingClipPath.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceClipPathAndStyle.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceClipPathAndStyle));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygon/polygon/point"_ostr, 20);
+ assertXPath(pDocument, "/primitive2D/transform/mask/mask/polypolygon/polygon/point"_ostr, 13);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testFillRule)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/FillRule.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor/polypolygon/polygon"_ostr, 2);
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/polypolygon/polygon"_ostr, 2);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testClipRule)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/ClipRule.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the place in place, this test would have failed with
+ // - Expected: 5
+ // - Actual : 10
+ assertXPath(pDocument, "/primitive2D/transform/mask[1]/polypolygon/polygon/point"_ostr, 5);
+ assertXPath(pDocument, "/primitive2D/transform/mask[1]/polypolygoncolor"_ostr, "color"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/mask[1]/polypolygoncolor/polypolygon/polygon/point"_ostr, 4);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask[2]/polypolygon/polygon/point"_ostr, 5);
+ assertXPath(pDocument, "/primitive2D/transform/mask[2]/polypolygoncolor"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/mask[2]/polypolygoncolor/polypolygon/polygon/point"_ostr, 4);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testi125329)
+{
+ //Check style inherit from * css element
+ Primitive2DSequence aSequencei125329 = parseSvg(u"/svgio/qa/cppunit/data/i125329.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequencei125329.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequencei125329));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor"_ostr, "color"_ostr, "#c0c0c0"); // rect background color
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "height"_ostr, "30"); // rect background height
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "width"_ostr, "50"); // rect background width
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "minx"_ostr, "15");
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "miny"_ostr, "15");
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "maxx"_ostr, "65");
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygoncolor/polypolygon"_ostr, "maxy"_ostr, "45");
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygonstroke/line"_ostr, "color"_ostr, "#008000"); // rect stroke color
+ assertXPath(pDocument, "/primitive2D/transform/transform/objectinfo/polypolygonstroke/line"_ostr, "width"_ostr, "1"); // rect stroke width
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testMaskingPath07b)
+{
+ //For the time being, check that masking-path-07-b.svg can be imported and it doesn't hang on loading
+ //it used to hang after d5649ae7b76278cb3155f951d6327157c7c92b65
+ Primitive2DSequence aSequenceMaskingPath07b = parseSvg(u"/svgio/qa/cppunit/data/masking-path-07-b.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceMaskingPath07b.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceMaskingPath07b));
+
+ CPPUNIT_ASSERT (pDocument);
+
+}
+
+CPPUNIT_TEST_FIXTURE(Test, test123926)
+{
+ Primitive2DSequence aSequence123926 = parseSvg(u"/svgio/qa/cppunit/data/tdf123926.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence123926.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence123926));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/unifiedtransparence/polypolygoncolor"_ostr, "color"_ostr, "#7cb5ec");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, test47446)
+{
+ //Check that marker's fill attribute is black is not set
+ Primitive2DSequence aSequence47446 = parseSvg(u"/svgio/qa/cppunit/data/47446.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence47446.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence47446));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#000000");
+
+}
+
+CPPUNIT_TEST_FIXTURE(Test, test47446b)
+{
+ //Check that marker's fill attribute is inherit from def
+ Primitive2DSequence aSequence47446b = parseSvg(u"/svgio/qa/cppunit/data/47446b.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence47446b.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence47446b));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#ffff00");
+
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf103888)
+{
+ Primitive2DSequence aSequenceMaskText = parseSvg(u"/svgio/qa/cppunit/data/tdf103888.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceMaskText.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceMaskText));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed here with number of nodes is incorrect
+ assertXPath(pDocument, "/primitive2D/transform/transform/textsimpleportion[1]"_ostr, "text"_ostr, "Her");
+ assertXPath(pDocument, "/primitive2D/transform/transform/textsimpleportion[2]"_ostr, "text"_ostr, "vor");
+ assertXPath(pDocument, "/primitive2D/transform/transform/textsimpleportion[3]"_ostr, "text"_ostr, "hebung");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156251)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156251.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 'You are '
+ // - Actual : 'You are'
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "You are");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, " not");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, " a banana!");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "text"_ostr, "You are");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "text"_ostr, " not");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "text"_ostr, " a banana!");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testMaskText)
+{
+ //Check that mask is applied on text
+ Primitive2DSequence aSequenceMaskText = parseSvg(u"/svgio/qa/cppunit/data/maskText.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceMaskText.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceMaskText));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/transform/polypolygoncolor"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/textsimpleportion"_ostr, "fontcolor"_ostr, "#ffffff");
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/textsimpleportion"_ostr, "text"_ostr, "Black White");
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/textsimpleportion"_ostr, "height"_ostr, "26");
+ assertXPath(pDocument, "/primitive2D/transform/transform/transform/textsimpleportion"_ostr, "familyname"_ostr, "Times New Roman");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf99994)
+{
+ //Check text fontsize when using relative units
+ Primitive2DSequence aSequenceTdf99994 = parseSvg(u"/svgio/qa/cppunit/data/tdf99994.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf99994.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf99994));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "test");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "DejaVu Sans");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf99115)
+{
+ //Check that styles are resolved correctly where there is a * css selector
+ Primitive2DSequence aSequenceTdf99115 = parseSvg(u"/svgio/qa/cppunit/data/tdf99115.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf99115.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf99115) );
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "red 1");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "red 2");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "fontcolor"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "red 3");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "fontcolor"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "text"_ostr, "blue 4");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "fontcolor"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "text"_ostr, "blue 5");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "fontcolor"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "text"_ostr, "blue 6");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "fontcolor"_ostr, "#0000ff");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "text"_ostr, "green 7");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "fontcolor"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "text"_ostr, "green 8");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "fontcolor"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "height"_ostr, "18");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "text"_ostr, "green 9");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "fontcolor"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "height"_ostr, "18");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf101237)
+{
+ //Check that fill color, stroke color and stroke-width are inherited from use element
+ //when the element is within a clipPath element
+ Primitive2DSequence aSequenceTdf101237 = parseSvg(u"/svgio/qa/cppunit/data/tdf101237.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf101237.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf101237));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor"_ostr, "color"_ostr, "#ff0000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygonstroke/line"_ostr, "width"_ostr, "5");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97710)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf97710.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[1]"_ostr, "color"_ostr, "#000000");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 100
+ // - Actual : 0
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[1]/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[1]/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[2]"_ostr, "color"_ostr, "#008000");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[2]/polypolygon"_ostr, "width"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygoncolor[2]/polypolygon"_ostr, "height"_ostr, "100");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygonstroke/line"_ostr, "color"_ostr, "#000000");
+ assertXPath(pDocument, "/primitive2D/transform/mask/polypolygonstroke/line"_ostr, "width"_ostr, "1");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf94765)
+{
+ Primitive2DSequence aSequenceTdf94765 = parseSvg(u"/svgio/qa/cppunit/data/tdf94765.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf94765.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf94765));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ //Check that both rectangles use the gradient as fill
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]"_ostr, "startx"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]"_ostr, "starty"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]"_ostr, "endx"_ostr, "2");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]"_ostr, "endy"_ostr, "1");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]"_ostr, "startx"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]"_ostr, "starty"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]"_ostr, "endx"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]"_ostr, "endy"_ostr, "0");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156236)
+{
+ Primitive2DSequence aSequenceTdf94765 = parseSvg(u"/svgio/qa/cppunit/data/tdf156236.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequenceTdf94765.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequenceTdf94765));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[1]/polypolygon"_ostr, "path"_ostr, "m50 180h-30v-60h60v60z");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[2]/polypolygon"_ostr, "path"_ostr, "m150 180h15c8.2842712474619 0 15-6.7157287525381 15-15v-30c0-8.2842712474619-6.7157287525381-15-15-15h-30c-8.2842712474619 0-15 6.7157287525381-15 15v30c0 8.2842712474619 6.7157287525381 15 15 15z");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[3]/polypolygon"_ostr, "path"_ostr, "m250 180h15c8.2842712474619 0 15-6.7157287525381 15-15v-30c0-8.2842712474619-6.7157287525381-15-15-15h-30c-8.2842712474619 0-15 6.7157287525381-15 15v30c0 8.2842712474619 6.7157287525381 15 15 15z");
+ assertXPath(pDocument, "/primitive2D/transform/polypolygoncolor[4]/polypolygon"_ostr, "path"_ostr, "m350 180c16.5685424949238 0 30-6.7157287525381 30-15v-30c0-8.2842712474619-13.4314575050762-15-30-15s-30 6.7157287525381-30 15v30c0 8.2842712474619 13.4314575050762 15 30 15z");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testBehaviourWhenWidthAndHeightIsOrIsNotSet)
+{
+ // This test checks the behaviour when width and height attributes
+ // are and are not set. In both cases the result must be the same,
+ // however if the width / height are set, then the size of the image
+ // is enforced, but this isn't really possible in LibreOffice (or
+ // maybe we could lock the size in this case).
+ // The behaviour in browsers is that when a SVG image has width / height
+ // attributes set, then the image is shown with that size, but if it
+ // isn't set then it is shown as scalable image which is the size of
+ // the container.
+
+ {
+ const Primitive2DSequence aSequence = parseSvg(u"svgio/qa/cppunit/data/Drawing_WithWidthHeight.svg");
+ CPPUNIT_ASSERT(aSequence.hasElements());
+
+ geometry::RealRectangle2D aRealRect;
+ basegfx::B2DRange aRange;
+ uno::Sequence<beans::PropertyValue> aViewParameters;
+
+ for (css::uno::Reference<css::graphic::XPrimitive2D> const & xReference : aSequence)
+ {
+ if (xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+ aRange.expand(basegfx::B2DRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2));
+ }
+ }
+
+ double fWidth = (aRange.getWidth() / 2540.0) * 96.0;
+ double fHeight = (aRange.getHeight() / 2540.0) * 96.0;
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, fWidth, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, fHeight, 1E-12);
+ }
+
+ {
+ const Primitive2DSequence aSequence = parseSvg(u"svgio/qa/cppunit/data/Drawing_NoWidthHeight.svg");
+ CPPUNIT_ASSERT(aSequence.hasElements());
+
+
+ geometry::RealRectangle2D aRealRect;
+ basegfx::B2DRange aRange;
+ uno::Sequence<beans::PropertyValue> aViewParameters;
+
+ for (css::uno::Reference<css::graphic::XPrimitive2D> const & xReference : aSequence)
+ {
+ if (xReference.is())
+ {
+ aRealRect = xReference->getRange(aViewParameters);
+ aRange.expand(basegfx::B2DRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2));
+ }
+ }
+
+ double fWidth = (aRange.getWidth() / 2540.0) * 96.0;
+ double fHeight = (aRange.getHeight() / 2540.0) * 96.0;
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, fWidth, 1E-12);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, fHeight, 1E-12);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf155733)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf155733.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/softedge"_ostr, "radius"_ostr, "5");
+
+ // Without the fix in place, the softedge would have been applied to the second element
+ // - Expected: 1
+ // - Actual : 0
+ assertXPath(pDocument, "/primitive2D/transform/transform/unifiedtransparence"_ostr, "transparence"_ostr, "50");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf97663)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/em_units.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // tdf#97663: Without the fix in place, this test would have failed with
+ // - Expected: 236
+ // - Actual : 204
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "236");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156269)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156269.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "//textsimpleportion[@text='one']"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "//textsimpleportion[@text='one']"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "//textsimpleportion[@text='one']"_ostr, "x"_ostr, "10");
+ assertXPath(pDocument, "//textsimpleportion[@text='one']"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "//textsimpleportion[@text='one']"_ostr, "fontcolor"_ostr, "#808080");
+
+ assertXPath(pDocument, "//textsimpleportion[@text='two']"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "//textsimpleportion[@text='two']"_ostr, "height"_ostr, "16");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 60
+ // - Actual : 10
+ assertXPath(pDocument, "//textsimpleportion[@text='two']"_ostr, "x"_ostr, "60");
+ assertXPath(pDocument, "//textsimpleportion[@text='two']"_ostr, "y"_ostr, "100");
+ assertXPath(pDocument, "//textsimpleportion[@text='two']"_ostr, "fontcolor"_ostr, "#000000");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf95400)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf95400.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "20");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "ABC");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx0"_ostr, "36");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx1"_ostr, "69");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx2"_ostr, "102");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "width"_ostr, "48");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "ABC");
+ assertXPathNoAttribute(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx0"_ostr);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTextAnchor)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf151103.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "43");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "x"_ostr, "26");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "x"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "x"_ostr, "43");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "x"_ostr, "26");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "x"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "x"_ostr, "43");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "x"_ostr, "26");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]"_ostr, "text"_ostr, "ABC");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "x"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[10]"_ostr, "text"_ostr, "A");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]"_ostr, "x"_ostr, "72");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[11]"_ostr, "text"_ostr, "B");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]"_ostr, "x"_ostr, "83");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[12]"_ostr, "text"_ostr, "C");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 43
+ // - Actual : 54
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]"_ostr, "x"_ostr, "43");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]"_ostr, "text"_ostr, "A");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]"_ostr, "x"_ostr, "55");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]"_ostr, "text"_ostr, "B");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]"_ostr, "x"_ostr, "66");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]"_ostr, "y"_ostr, "50");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]"_ostr, "text"_ostr, "C");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]"_ostr, "x"_ostr, "26");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]"_ostr, "text"_ostr, "A");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]"_ostr, "x"_ostr, "38");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]"_ostr, "text"_ostr, "B");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]"_ostr, "x"_ostr, "49");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]"_ostr, "y"_ostr, "60");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]"_ostr, "text"_ostr, "C");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156577)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156577.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "20");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "ABC");
+ assertXPathNoAttribute(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx0"_ostr);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "ABC");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 22
+ // - Actual : 52
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx0"_ostr, "22");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx1"_ostr, "53");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx2"_ostr, "94");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156283)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156283.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "20");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "ABC");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx0"_ostr, "41");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx1"_ostr, "52");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx2"_ostr, "63");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "ABC");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 41
+ // - Actual : 12
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx0"_ostr, "41");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx1"_ostr, "52");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx2"_ostr, "63");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156569)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156569.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "20");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "ABC");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx0"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx1"_ostr, "80");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "dx2"_ostr, "91");
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "0");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, "ABC");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx0"_ostr, "40");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 80
+ // - Actual : 51
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx1"_ostr, "80");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "dx2"_ostr, "91");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156837)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156837.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion"_ostr, 2);
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "x"_ostr, "114");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "y"_ostr, "103");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "x");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "x"_ostr, "122");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 94
+ // - Actual : 103
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "y"_ostr, "94");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "height"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, "text"_ostr, " 3");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf156271)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf156271.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "x"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "y"_ostr, "10");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "text"_ostr, "AB");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "dx0"_ostr, "-30");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]"_ostr, "dx1"_ostr, "-19");
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "x"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "y"_ostr, "20");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "text"_ostr, "AB");
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: -30
+ // - Actual : 0
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "dx0"_ostr, "-30");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]"_ostr, "dx1"_ostr, "-19");
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "x"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "y"_ostr, "30");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "text"_ostr, "AB");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "dx0"_ostr, "-30");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]"_ostr, "dx1"_ostr, "-19");
+
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "width"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "height"_ostr, "16");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "x"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "y"_ostr, "40");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "text"_ostr, "AB");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "dx0"_ostr, "12");
+ assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]"_ostr, "dx1"_ostr, "23");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf149880)
+{
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/tdf149880.svg");
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+
+ CPPUNIT_ASSERT (pDocument);
+
+ // Without the fix in place, this test would have failed with
+ // - Expected: 1
+ // - Actual : 0
+ // - In <>, XPath '/primitive2D/transform/mask/unhandled' number of nodes is incorrect
+ assertXPath(pDocument,
+ "/primitive2D/transform/mask/unhandled"_ostr, "id"_ostr, "PATTERNFILL");
+ assertXPath(pDocument,
+ "/primitive2D/transform/mask/unhandled/mask/transform/transform/bitmap"_ostr, 28);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testCssClassRedefinition)
+{
+ // Tests for svg css class redefinition behavior
+ // Example:
+ // .c1 {fill:#00ff00}
+ // .c1 {font-family:Sans}
+ // .c1 {fill:#ff0000}
+ // Expected result is .c1 {font-family:Sans; fill:#ff0000} because
+ // the second redefinition appends attributes to the class and the
+ // third redefinition replaces the already existing
+ // attribute in the original definition
+ Primitive2DSequence aSequence = parseSvg(u"/svgio/qa/cppunit/data/CssClassRedefinition.svg");
+ drawinglayer::Primitive2dXmlDump dumper;
+ xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
+ CPPUNIT_ASSERT (pDocument);
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "text"_ostr, "012");
+ assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "fontcolor"_ostr, "#ff0000");
+ assertXPath(
+ pDocument, "/primitive2D/transform/textsimpleportion[1]"_ostr, "familyname"_ostr, "Open Symbol");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTspanFillOpacity)
+{
+ // Given an SVG file with <tspan fill-opacity="0.30">:
+ std::u16string_view aPath = u"/svgio/qa/cppunit/data/tspan-fill-opacity.svg";
+
+ // When rendering that SVG:
+ Primitive2DSequence aSequence = parseSvg(aPath);
+
+ // Then make sure that the text portion is wrapped in a transparency primitive with the correct
+ // transparency value:
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(Primitive2DContainer(aSequence));
+ sal_Int32 nTransparence = getXPath(pDocument, "//textsimpleportion[@text='hello']/parent::unifiedtransparence"_ostr, "transparence"_ostr).toInt32();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // - XPath '//textsimpleportion[@text='hello']/parent::unifiedtransparence' number of nodes is incorrect
+ // i.e. the relevant <textsimpleportion> had no <unifiedtransparence> parent, the text was not
+ // semi-transparent.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(70), nTransparence);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/qa/cppunit/SvgNumberTest.cxx b/svgio/qa/cppunit/SvgNumberTest.cxx
new file mode 100644
index 0000000000..f420a44b42
--- /dev/null
+++ b/svgio/qa/cppunit/SvgNumberTest.cxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <SvgNumber.hxx>
+
+namespace
+{
+class TestNumber : public CppUnit::TestFixture
+{
+ void testSetting();
+ void testSolve();
+
+public:
+ CPPUNIT_TEST_SUITE(TestNumber);
+ CPPUNIT_TEST(testSetting);
+ CPPUNIT_TEST(testSolve);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+class TestInfoProvider : public svgio::svgreader::InfoProvider
+{
+public:
+ basegfx::B2DRange getCurrentViewPort() const override
+ {
+ return basegfx::B2DRange(0.0, 0.0, 0.0, 0.0);
+ }
+
+ double getCurrentFontSizeInherited() const override { return 12.0; }
+
+ double getCurrentXHeightInherited() const override { return 5.0; }
+};
+
+void TestNumber::testSetting()
+{
+ {
+ svgio::svgreader::SvgNumber aNumber;
+ CPPUNIT_ASSERT_EQUAL(svgio::svgreader::SvgUnit::px, aNumber.getUnit());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aNumber.getNumber(), 1e-8);
+ CPPUNIT_ASSERT_EQUAL(false, aNumber.isSet());
+ }
+ {
+ svgio::svgreader::SvgNumber aNumber(0.01);
+ CPPUNIT_ASSERT_EQUAL(svgio::svgreader::SvgUnit::px, aNumber.getUnit());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.01, aNumber.getNumber(), 1e-8);
+ CPPUNIT_ASSERT_EQUAL(true, aNumber.isSet());
+ }
+ {
+ svgio::svgreader::SvgNumber aNumber(1.01, svgio::svgreader::SvgUnit::cm);
+ CPPUNIT_ASSERT_EQUAL(svgio::svgreader::SvgUnit::cm, aNumber.getUnit());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.01, aNumber.getNumber(), 1e-8);
+ CPPUNIT_ASSERT_EQUAL(true, aNumber.isSet());
+ }
+}
+
+void TestNumber::testSolve()
+{
+ {
+ svgio::svgreader::SvgNumber aNumber(1.01);
+ TestInfoProvider aInfoProvider;
+ double aSolvedNumber = aNumber.solve(aInfoProvider);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.01, aSolvedNumber, 1e-8);
+ }
+ {
+ svgio::svgreader::SvgNumber aNumber(1.0, svgio::svgreader::SvgUnit::pt);
+ TestInfoProvider aInfoProvider;
+ double aSolvedNumber = aNumber.solve(aInfoProvider);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.333, aSolvedNumber, 1e-3);
+ }
+ {
+ svgio::svgreader::SvgNumber aNumber(2.54, svgio::svgreader::SvgUnit::cm);
+ TestInfoProvider aInfoProvider;
+ double aSolvedNumber = aNumber.solve(aInfoProvider);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(96.0, aSolvedNumber, 1e-3);
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestNumber);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/qa/cppunit/SvgRead.cxx b/svgio/qa/cppunit/SvgRead.cxx
new file mode 100644
index 0000000000..a26556dd7a
--- /dev/null
+++ b/svgio/qa/cppunit/SvgRead.cxx
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <test/bootstrapfixture.hxx>
+
+#include <memory>
+
+#include <comphelper/seqstream.hxx>
+#include <comphelper/processfactory.hxx>
+#include <tools/stream.hxx>
+
+#include <com/sun/star/graphic/SvgTools.hpp>
+#include <com/sun/star/graphic/XSvgParser.hpp>
+
+#include <basegfx/DrawCommands.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+namespace
+{
+using namespace css;
+
+class TestParsing : public test::BootstrapFixture
+{
+ void testSimpleRectangle();
+ void testPath();
+ uno::Reference<io::XInputStream> parseSvg(const OUString& aSource);
+
+public:
+ CPPUNIT_TEST_SUITE(TestParsing);
+ CPPUNIT_TEST(testSimpleRectangle);
+ CPPUNIT_TEST(testPath);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+uno::Reference<io::XInputStream> TestParsing::parseSvg(const OUString& aSource)
+{
+ SvFileStream aFileStream(aSource, StreamMode::READ);
+ std::size_t nSize = aFileStream.remainingSize();
+ std::unique_ptr<sal_Int8[]> pBuffer(new sal_Int8[nSize + 1]);
+ aFileStream.ReadBytes(pBuffer.get(), nSize);
+ pBuffer[nSize] = 0;
+
+ uno::Sequence<sal_Int8> aData(pBuffer.get(), nSize + 1);
+ uno::Reference<io::XInputStream> aInputStream(new comphelper::SequenceInputStream(aData));
+
+ return aInputStream;
+}
+
+void TestParsing::testSimpleRectangle()
+{
+ OUString aSvgFile = "/svgio/qa/cppunit/data/VisiotorTest-Rect.svg";
+ OUString aUrl = m_directories.getURLFromSrc(aSvgFile);
+ OUString aPath = m_directories.getPathFromSrc(aSvgFile);
+
+ uno::Reference<io::XInputStream> xStream = parseSvg(aUrl);
+ CPPUNIT_ASSERT(xStream.is());
+
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser = graphic::SvgTools::create(xContext);
+
+ uno::Any aAny = xSvgParser->getDrawCommands(xStream, aPath);
+ CPPUNIT_ASSERT(aAny.has<sal_uInt64>());
+ auto* pDrawRoot = reinterpret_cast<gfx::DrawRoot*>(aAny.get<sal_uInt64>());
+
+ basegfx::B2DRange aSurfaceRectangle(0, 0, 120, 120);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pDrawRoot->maChildren.size());
+ CPPUNIT_ASSERT_EQUAL(aSurfaceRectangle, pDrawRoot->maRectangle);
+
+ auto* pDrawBase = pDrawRoot->maChildren[0].get();
+ CPPUNIT_ASSERT_EQUAL(gfx::DrawCommandType::Rectangle, pDrawRoot->maChildren[0]->getType());
+ auto* pDrawRect = static_cast<gfx::DrawRectangle*>(pDrawBase);
+ CPPUNIT_ASSERT_EQUAL(basegfx::B2DRange(10, 10, 110, 110), pDrawRect->maRectangle);
+ CPPUNIT_ASSERT_EQUAL(3.0, pDrawRect->mnStrokeWidth);
+ CPPUNIT_ASSERT(bool(pDrawRect->mpStrokeColor));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(*pDrawRect->mpStrokeColor));
+ CPPUNIT_ASSERT(bool(pDrawRect->mpFillColor));
+ CPPUNIT_ASSERT_EQUAL(Color(0x00cc00), Color(*pDrawRect->mpFillColor));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.1, pDrawRect->mnOpacity, 1E-12);
+}
+
+void TestParsing::testPath()
+{
+ OUString aSvgFile = "/svgio/qa/cppunit/data/path.svg";
+ OUString aUrl = m_directories.getURLFromSrc(aSvgFile);
+ OUString aPath = m_directories.getPathFromSrc(aSvgFile);
+
+ uno::Reference<io::XInputStream> xStream = parseSvg(aUrl);
+ CPPUNIT_ASSERT(xStream.is());
+
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ const uno::Reference<graphic::XSvgParser> xSvgParser = graphic::SvgTools::create(xContext);
+
+ uno::Any aAny = xSvgParser->getDrawCommands(xStream, aPath);
+ CPPUNIT_ASSERT(aAny.has<sal_uInt64>());
+ auto* pDrawRoot = reinterpret_cast<gfx::DrawRoot*>(aAny.get<sal_uInt64>());
+
+ CPPUNIT_ASSERT_EQUAL(size_t(1), pDrawRoot->maChildren.size());
+
+ auto* pDrawBase = pDrawRoot->maChildren[0].get();
+ CPPUNIT_ASSERT_EQUAL(gfx::DrawCommandType::Path, pDrawBase->getType());
+ auto* pDrawPath = static_cast<gfx::DrawPath*>(pDrawBase);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("m1 1h42v24h-42v-24z"),
+ basegfx::utils::exportToSvgD(pDrawPath->maPolyPolygon, true, true, false));
+ CPPUNIT_ASSERT_EQUAL(0.0, pDrawPath->mnStrokeWidth);
+ CPPUNIT_ASSERT(bool(pDrawPath->mpStrokeColor));
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, Color(*pDrawPath->mpStrokeColor));
+ CPPUNIT_ASSERT(bool(pDrawPath->mpFillColor));
+ CPPUNIT_ASSERT_EQUAL(Color(0x007aff), Color(*pDrawPath->mpFillColor));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.1, pDrawPath->mnOpacity, 1E-12);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestParsing);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/qa/cppunit/data/47446.svg b/svgio/qa/cppunit/data/47446.svg
new file mode 100644
index 0000000000..aec66b9bdc
--- /dev/null
+++ b/svgio/qa/cppunit/data/47446.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" standalone="no" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="12cm" height="4cm" viewBox="0 0 1200 400"
+ xmlns="http://www.w3.org/2000/svg" version="1.1">
+<defs>
+ <marker id="Triangle"
+ markerUnits="strokeWidth" refY="3"
+ markerWidth="6" markerHeight="6"
+ orient="auto">
+ <path d="M 0 0 L 6 3 L 0 6 z"
+ stroke-width="0.5" stroke="black"/>
+ </marker>
+</defs>
+
+<polyline fill="none" stroke="red" stroke-width="10"
+ points="450,50 550,50 650,150"
+ marker-end="url(#Triangle)" />
+</svg>
diff --git a/svgio/qa/cppunit/data/47446b.svg b/svgio/qa/cppunit/data/47446b.svg
new file mode 100644
index 0000000000..29cfce5d84
--- /dev/null
+++ b/svgio/qa/cppunit/data/47446b.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" standalone="no" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="12cm" height="4cm" viewBox="0 0 1200 400"
+ xmlns="http://www.w3.org/2000/svg" version="1.1">
+<defs fill="yellow">
+ <marker id="Triangle"
+ markerUnits="strokeWidth" refY="3"
+ markerWidth="6" markerHeight="6"
+ orient="auto">
+ <path d="M 0 0 L 6 3 L 0 6 z"
+ stroke-width="0.5" stroke="black"/>
+ </marker>
+</defs>
+<polyline fill="none" stroke="red" stroke-width="10"
+ points="450,50 550,50 650,150"
+ marker-end="url(#Triangle)" />
+</svg>
diff --git a/svgio/qa/cppunit/data/ClipPathAndParentStyle.svg b/svgio/qa/cppunit/data/ClipPathAndParentStyle.svg
new file mode 100644
index 0000000000..d85a95995c
--- /dev/null
+++ b/svgio/qa/cppunit/data/ClipPathAndParentStyle.svg
@@ -0,0 +1,10 @@
+<svg version="1.1" baseProfile="basic" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <clipPath>
+ <circle id="c1" cx="100" cy="100" r="50"/>
+ </clipPath>
+
+ <use xlink:href="#c1" style="fill:red" stroke-width="5px" stroke="black"/>
+
+</svg>
diff --git a/svgio/qa/cppunit/data/ClipPathAndStyle.svg b/svgio/qa/cppunit/data/ClipPathAndStyle.svg
new file mode 100644
index 0000000000..f3b1777fa5
--- /dev/null
+++ b/svgio/qa/cppunit/data/ClipPathAndStyle.svg
@@ -0,0 +1,13 @@
+<svg version="1.1" baseProfile="basic" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <clipPath>
+ <circle id="c1" cx="100" cy="100" r="50"
+ style="stroke: #0000cc;
+ stroke-width: 2px;
+ fill : #ccccff;"/>
+ </clipPath>
+
+ <use href="#c1" style="fill:red" stroke-width="5px" stroke="black"/>
+
+</svg>
diff --git a/svgio/qa/cppunit/data/ClipPathUsingClipPath.svg b/svgio/qa/cppunit/data/ClipPathUsingClipPath.svg
new file mode 100644
index 0000000000..5eaa7928cb
--- /dev/null
+++ b/svgio/qa/cppunit/data/ClipPathUsingClipPath.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<defs>
+
+<clipPath id="clip1">
+ <polygon id="clip1Shape" points="100,10 40,180 190,60 10,60 160,180 100,10" stroke="blue" />
+</clipPath>
+
+<clipPath id="clip2">
+ <circle id="clip2Shape" cx="100" cy="100" r="65" />
+</clipPath>
+
+
+<clipPath id="clipIntersection" clip-path="url(#clip1)">
+ <use x="0" y="0" width="200" height="200" xlink:href="#clip2Shape" />
+</clipPath>
+
+</defs>
+
+<rect x="10" y="10" width="180" height="180" fill="red"
+ clip-path="url(#clipIntersection)" transform="translate(200)" />
+
+</svg>
diff --git a/svgio/qa/cppunit/data/ClipRule.svg b/svgio/qa/cppunit/data/ClipRule.svg
new file mode 100644
index 0000000000..55f0cb9eee
--- /dev/null
+++ b/svgio/qa/cppunit/data/ClipRule.svg
@@ -0,0 +1,18 @@
+<svg version="1.1" baseProfile="basic" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Define star path -->
+ <defs>
+ <path d="M50,0 21,90 98,35 2,35 79,90z" id="star" />
+ </defs>
+ <clipPath id="emptyStar">
+ <use xlink:href="#star" clip-rule="evenodd" />
+ </clipPath>
+ <rect clip-path="url(#emptyStar)" width="50" height="90" fill="blue" />
+
+ <clipPath id="filledStar">
+ <use xlink:href="#star" clip-rule="evenodd" />
+ </clipPath>
+ <rect clip-path="url(#filledStar)" width="50" height="90" x="50" fill="red" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/CssClassRedefinition.svg b/svgio/qa/cppunit/data/CssClassRedefinition.svg
new file mode 100644
index 0000000000..591f07f8b4
--- /dev/null
+++ b/svgio/qa/cppunit/data/CssClassRedefinition.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="50 px" height="100 px">
+ <style type="text/css">
+ .c1 {fill:#00ff00}
+ <!-- this redefinition should be appended -->
+ .c1 {font-family:Open Symbol}
+ <!-- this redefinition should change the color to red, replacing the first definition -->
+ .c1 {fill:#ff0000}
+ <!-- finally c1 should be equal to fill:#ff0000 and font-family:Sans -->
+ </style>
+ <text x="20" y="20" >
+ <tspan class="c1">012</tspan>
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/Drawing_NoWidthHeight.svg b/svgio/qa/cppunit/data/Drawing_NoWidthHeight.svg
new file mode 100644
index 0000000000..59520d6ab9
--- /dev/null
+++ b/svgio/qa/cppunit/data/Drawing_NoWidthHeight.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 10 10">
+ <rect
+ width="3"
+ height="3"
+ x="6"
+ y="6"
+ style="fill:#008000;stroke-width:0.15288568" />
+</svg>
diff --git a/svgio/qa/cppunit/data/Drawing_WithWidthHeight.svg b/svgio/qa/cppunit/data/Drawing_WithWidthHeight.svg
new file mode 100644
index 0000000000..bc5afb553e
--- /dev/null
+++ b/svgio/qa/cppunit/data/Drawing_WithWidthHeight.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ viewBox="0 0 10 10"
+ height="10"
+ width="10">
+ <rect
+ width="3"
+ height="3"
+ x="6"
+ y="6"
+ style="fill:#008000;stroke-width:0.15288568" />
+</svg>
diff --git a/svgio/qa/cppunit/data/FillRule.svg b/svgio/qa/cppunit/data/FillRule.svg
new file mode 100644
index 0000000000..c406065fbe
--- /dev/null
+++ b/svgio/qa/cppunit/data/FillRule.svg
@@ -0,0 +1,6 @@
+<svg viewBox="-10 -10 320 120" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" stroke="red"
+ d="M110,0 h90 v90 h-90 z
+ M130,20 h50 v50 h-50 z"/>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/FontsizeKeywords.svg b/svgio/qa/cppunit/data/FontsizeKeywords.svg
new file mode 100644
index 0000000000..9a97983c01
--- /dev/null
+++ b/svgio/qa/cppunit/data/FontsizeKeywords.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="600" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<text x="5" y="15" font-size="xx-small" fill="black">Sample</text>
+<text x="5" y="50" font-size="x-small" fill="white">Sample</text>
+<text x="5" y="100" font-size="small" fill="gold">Sample</text>
+<text x="5" y="150" font-size="medium" fill="red">Sample</text>
+<text x="5" y="200" font-size="large" fill="yellow">Sample</text>
+<text x="5" y="250" font-size="x-large" fill="blue">Sample</text>
+<text x="5" y="300" font-size="xx-large" fill="green">Sample</text>
+<text x="5" y="350" font-size="smaller" fill="coral">Sample</text>
+<text x="5" y="400" font-size="larger" fill="pink">Sample</text>
+<text x="5" y="450" font-size="initial" fill="ivory">Sample</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/FontsizePercentage.svg b/svgio/qa/cppunit/data/FontsizePercentage.svg
new file mode 100644
index 0000000000..fc7c9fa61c
--- /dev/null
+++ b/svgio/qa/cppunit/data/FontsizePercentage.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?> <svg height="600" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" font-size="100%">
+ <text x="5" y="15">Sample</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/FontsizeRelative.svg b/svgio/qa/cppunit/data/FontsizeRelative.svg
new file mode 100644
index 0000000000..4b74aa692b
--- /dev/null
+++ b/svgio/qa/cppunit/data/FontsizeRelative.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?> <svg height="600" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g font-size="5px" font-family="DejaVu Serif">
+ <text x="10" y="150" font-size="10em" font-family="inherit">Sample</text>
+ <text x="200" y="150" font-size="10em" font-family="DejaVu Serif">Sample</text>
+</g>
+</svg>
diff --git a/svgio/qa/cppunit/data/MarkerOrient.svg b/svgio/qa/cppunit/data/MarkerOrient.svg
new file mode 100644
index 0000000000..7997e1cce9
--- /dev/null
+++ b/svgio/qa/cppunit/data/MarkerOrient.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <!-- arrowhead marker definition -->
+ <marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5"
+ markerWidth="6" markerHeight="6"
+ orient="auto-start-reverse">
+ <path d="M 0 0 L 10 5 L 0 10 z" />
+ </marker>
+
+ <marker id="arrow2" viewBox="0 0 10 10" refX="5" refY="5"
+ markerWidth="6" markerHeight="6"
+ orient="auto-start-reverse">
+ <path d="M 0 0 L 10 5 L 0 10 z" />
+ </marker>
+
+ </defs>
+
+ <!-- Coordinate axes with a arrowhead in both direction -->
+ <polyline points="10,10 10,90 90,90" fill="none" stroke="black"
+ marker-start="url(#arrow)" marker-end="url(#arrow2)" />
+</svg>
diff --git a/svgio/qa/cppunit/data/RGBAColor.svg b/svgio/qa/cppunit/data/RGBAColor.svg
new file mode 100644
index 0000000000..ddd7a3cc03
--- /dev/null
+++ b/svgio/qa/cppunit/data/RGBAColor.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" fill="rgba(20,10,100,0.5)" visibility="inherit" />
+</svg>
diff --git a/svgio/qa/cppunit/data/RGBColor.svg b/svgio/qa/cppunit/data/RGBColor.svg
new file mode 100644
index 0000000000..ad60d5b55a
--- /dev/null
+++ b/svgio/qa/cppunit/data/RGBColor.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" fill="rgb(100,100,100)" visibility="inherit" />
+</svg>
diff --git a/svgio/qa/cppunit/data/Rect.svg b/svgio/qa/cppunit/data/Rect.svg
new file mode 100644
index 0000000000..7567cdfb5b
--- /dev/null
+++ b/svgio/qa/cppunit/data/Rect.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" stroke="#ff0000" fill="#00cc00" stroke-width="3" />
+</svg>
diff --git a/svgio/qa/cppunit/data/RectWithParentStyles.svg b/svgio/qa/cppunit/data/RectWithParentStyles.svg
new file mode 100644
index 0000000000..a01ba3ff5b
--- /dev/null
+++ b/svgio/qa/cppunit/data/RectWithParentStyles.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <style type="text/css" >
+ <![CDATA[
+ svg
+ {
+ stroke: #ff0000;
+ fill: #00cc00;
+ }
+ ]]>
+ </style>
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" style="stroke-width: 3;" />
+</svg>
diff --git a/svgio/qa/cppunit/data/RectWithStyles.svg b/svgio/qa/cppunit/data/RectWithStyles.svg
new file mode 100644
index 0000000000..b7068499be
--- /dev/null
+++ b/svgio/qa/cppunit/data/RectWithStyles.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" style="stroke: #ff0000; fill: #00cc00; stroke-width: 3" />
+</svg>
diff --git a/svgio/qa/cppunit/data/RectWithStylesByGroup.svg b/svgio/qa/cppunit/data/RectWithStylesByGroup.svg
new file mode 100644
index 0000000000..0a3b1e3cd8
--- /dev/null
+++ b/svgio/qa/cppunit/data/RectWithStylesByGroup.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ g
+ {
+ stroke: #ff0000;
+ fill: #00cc00;
+ }
+ ]]>
+ </style>
+ </defs>
+ <g>
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" style="stroke-width: 3;" />
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/ShapeWithClipPath.svg b/svgio/qa/cppunit/data/ShapeWithClipPath.svg
new file mode 100644
index 0000000000..28c51b4dfb
--- /dev/null
+++ b/svgio/qa/cppunit/data/ShapeWithClipPath.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="120" height="120"
+ viewPort="0 0 120 120" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <clipPath id="myClip">
+ <rect x="30" y="30" width="20" height="20"/>
+ <rect x="70" y="70" width="20" height="20"/>
+ </clipPath>
+
+ <rect x="10" y="10" width="100" height="100" fill="#00D000"
+ clip-path="url(#myClip)"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/ShapeWithClipPathAndCssStyle.svg b/svgio/qa/cppunit/data/ShapeWithClipPathAndCssStyle.svg
new file mode 100644
index 0000000000..4b6455c649
--- /dev/null
+++ b/svgio/qa/cppunit/data/ShapeWithClipPathAndCssStyle.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<svg width="120" height="120"
+ viewPort="0 0 120 120" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <clipPath id="myClip">
+ <rect x="30" y="30" width="20" height="20"/>
+ <rect x="70" y="70" width="20" height="20"/>
+ </clipPath>
+
+ <rect x="10" y="10" width="100" height="100" style="fill:#00D000"
+ clip-path="url(#myClip)"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/VisiotorTest-Rect.svg b/svgio/qa/cppunit/data/VisiotorTest-Rect.svg
new file mode 100644
index 0000000000..4cd2d3602e
--- /dev/null
+++ b/svgio/qa/cppunit/data/VisiotorTest-Rect.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" stroke="#ff0000" opacity="0.1" fill="#00cc00" stroke-width="3" />
+</svg>
diff --git a/svgio/qa/cppunit/data/em_units.svg b/svgio/qa/cppunit/data/em_units.svg
new file mode 100644
index 0000000000..6a7947cb3c
--- /dev/null
+++ b/svgio/qa/cppunit/data/em_units.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="10cm" height="10cm">
+
+ <style>
+ text {font-family: 'DejaVu Sans'; font-size: 36pt;}
+ new {font-family: 'DejaVu Sans'; font-size: 1em;}
+ </style>
+
+ <line x1="5cm" y1="5cm" x2="8cm" y2="5cm" stroke="black" />
+ <!-- 0.5in = 1.27cm = 36pt !-->
+ <line x1="5cm" y1="6.27cm" x2="8cm" y2="6.27cm" stroke="black" />
+ <text x="5cm" y="5cm" class="new">AWlll<tspan x="5cm" dy="1em">AWlll</tspan>
+ </text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeColorMatrix.svg b/svgio/qa/cppunit/data/filterFeColorMatrix.svg
new file mode 100644
index 0000000000..a86c2debc2
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeColorMatrix.svg
@@ -0,0 +1,59 @@
+<svg
+ width="100%"
+ height="100%"
+ viewBox="0 0 150 500"
+ preserveAspectRatio="xMidYMid meet"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- ref -->
+ <defs>
+ <g id="circles">
+ <circle cx="30" cy="30" r="20" fill="blue" fill-opacity="0.5" />
+ </g>
+ </defs>
+ <use href="#circles" />
+ <text x="70" y="50">Reference</text>
+
+ <!-- Combine RGB into green matrix -->
+ <filter id="colorMeGreen">
+ <feColorMatrix
+ in="SourceGraphic"
+ values="0 0 0 0 0
+ 1 1 1 1 0
+ 0 0 0 0 0
+ 0 0 0 1 0" />
+ </filter>
+ <use
+ href="#circles"
+ transform="translate(0 70)"
+ filter="url(#colorMeGreen)" />
+ <text x="70" y="120">rgbToGreen</text>
+
+ <!-- saturate -->
+ <filter id="colorMeSaturate">
+ <feColorMatrix in="SourceGraphic" type="saturate" values="0.2" />
+ </filter>
+ <use
+ href="#circles"
+ transform="translate(0 140)"
+ filter="url(#colorMeSaturate)" />
+ <text x="70" y="190">saturate</text>
+
+ <!-- hueRotate -->
+ <filter id="colorMeHueRotate">
+ <feColorMatrix in="SourceGraphic" type="hueRotate" values="180" />
+ </filter>
+ <use
+ href="#circles"
+ transform="translate(0 210)"
+ filter="url(#colorMeHueRotate)" />
+ <text x="70" y="260">hueRotate</text>
+
+ <!-- luminanceToAlpha -->
+ <filter id="colorMeLTA">
+ <feColorMatrix in="SourceGraphic" type="luminanceToAlpha" />
+ </filter>
+ <use href="#circles" transform="translate(0 280)" filter="url(#colorMeLTA)" />
+ <text x="70" y="330">luminanceToAlpha</text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeDropShadow.svg b/svgio/qa/cppunit/data/filterFeDropShadow.svg
new file mode 100644
index 0000000000..a6405c93af
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeDropShadow.svg
@@ -0,0 +1,10 @@
+<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <filter id="shadow">
+ <feDropShadow dx="0.6" dy="0.4" stdDeviation="0.2" flood-color="blue" flood-opacity="0.5" />
+ </filter>
+ </defs>
+
+ <circle cx="5" cy="50%" r="4" style="fill:pink; filter:url(#shadow);" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeFlood.svg b/svgio/qa/cppunit/data/filterFeFlood.svg
new file mode 100644
index 0000000000..2c438ad1a9
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeFlood.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
+ <defs>
+ <filter id="floodFilter" filterUnits="userSpaceOnUse">
+ <feFlood
+ x="50"
+ y="50"
+ width="100"
+ height="100"
+ flood-color="green"
+ flood-opacity="0.5" />
+ </filter>
+ </defs>
+
+ <use filter="url(#floodFilter)" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeGaussianBlur.svg b/svgio/qa/cppunit/data/filterFeGaussianBlur.svg
new file mode 100644
index 0000000000..e8fd73068a
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeGaussianBlur.svg
@@ -0,0 +1,11 @@
+<svg
+ width="230"
+ height="120"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <filter id="blurMe">
+ <feGaussianBlur in="SourceGraphic" stdDeviation="5"/>
+ </filter>
+ <circle cx="170" cy="60" r="50" fill="green" filter="url(#blurMe)" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeImage.svg b/svgio/qa/cppunit/data/filterFeImage.svg
new file mode 100644
index 0000000000..5682dbf469
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeImage.svg
@@ -0,0 +1,16 @@
+<svg
+ viewBox="0 0 200 200"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="200"
+ height="200">
+ <defs>
+ <filter id="image">
+ <feImage
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIEAAACBCAYAAADnoNlQAAAABHNCSVQICAgIfAhkiAAAAUJJREFUeJzt3EENAkEQRcG3aMAjMpYzBgjqSDCyWJhbZ5IqBf/w0sc+6rzaxnN6wLpzesC62/QA5okAESACEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAiojvv12+at7aPP9IRlO211CRABIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAdVzvtnlr22t6wLrnd3rBOpcAESACEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBFR/ALUM7y9VYLsAAAAASUVORK5CYII="/>
+ </filter>
+ </defs>
+
+ <rect x="10%" y="10%" width="80%" height="80%" style="filter:url(#image);" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/filterFeOffset.svg b/svgio/qa/cppunit/data/filterFeOffset.svg
new file mode 100644
index 0000000000..89bb40eef8
--- /dev/null
+++ b/svgio/qa/cppunit/data/filterFeOffset.svg
@@ -0,0 +1,18 @@
+<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <filter id="offset" width="180" height="180">
+ <feOffset in="SourceGraphic" dx="44" dy="66" />
+ </filter>
+ </defs>
+
+ <rect x="0" y="0" width="100" height="100" stroke="black" fill="green" />
+ <rect
+ x="0"
+ y="0"
+ width="100"
+ height="100"
+ stroke="black"
+ fill="green"
+ filter="url(#offset)" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/i125329.svg b/svgio/qa/cppunit/data/i125329.svg
new file mode 100644
index 0000000000..86e3bd8393
--- /dev/null
+++ b/svgio/qa/cppunit/data/i125329.svg
@@ -0,0 +1,12 @@
+<svg version="1.1" baseProfile="full" id="svg-root"
+ width="12cm" height="6cm" viewBox="0 0 120 60"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <title id="test-title">all selector</title>
+ <style type="text/css">
+ * {fill:silver; stroke-width:1;stroke-miterlimit:100000;}
+ rect {stroke:green;}
+ #test-frame {stroke:blue; fill:none;}
+ </style>
+ <rect x="15" y="15" width="50" height="30" stroke-width="10"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/markerInCssStyle.svg b/svgio/qa/cppunit/data/markerInCssStyle.svg
new file mode 100644
index 0000000000..a7a8374f6e
--- /dev/null
+++ b/svgio/qa/cppunit/data/markerInCssStyle.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<defs>
+<marker style="overflow:visible;" id="bip" refX="0" refY="0" orient="auto">
+ <path style="stroke: green" d="M 0,-3 v 6" />
+</marker>
+</defs>
+
+<style>
+path.boundary {stroke: red; fill: #ccc; stroke-width: 3; marker-mid: url(#bip); marker-end: url(#bip)}
+</style>
+
+<path class="boundary" d="m 20,20 v 90 90 90 90 90 h 90 90 90 90 90 v -90 -90 -90 -90 -90 h -90 -90 -90 -90 z" />
+</svg>
diff --git a/svgio/qa/cppunit/data/markerInPresentation.svg b/svgio/qa/cppunit/data/markerInPresentation.svg
new file mode 100644
index 0000000000..5071544e39
--- /dev/null
+++ b/svgio/qa/cppunit/data/markerInPresentation.svg
@@ -0,0 +1,12 @@
+<svg version="1.1" baseProfile="full" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <marker id="marker2" markerUnits="strokeWidth" refX="100" refY="100" markerWidth="15" markerHeight="15" viewBox="0 0 200 200">
+ <rect width="200" height="200" fill="red" stroke="none"/>
+ </marker>
+ </defs>
+ <g marker="url(#marker2)" fill="gold" stroke="black" fill-rule="evenodd" transform="translate(50,20)">
+ <line x1="370" x2="280" y1="60" y2="140"/>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/maskText.svg b/svgio/qa/cppunit/data/maskText.svg
new file mode 100644
index 0000000000..7405f6a569
--- /dev/null
+++ b/svgio/qa/cppunit/data/maskText.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<svg width="200" height="80"
+ viewBox="0 0 200 80" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <defs>
+ <mask id="myMask"
+ x="0" y="0" width="200" height="80">
+ <rect x="0" y="0" width="100" height="80" fill="white"/>
+ </mask>
+
+ <text id="Text" x="100" y="48"
+ font-size="26" font-weight="bold" text-anchor="middle">
+ Black White
+ </text>
+ </defs>
+
+ <!-- Draw black rectangle in the background -->
+ <rect x="100" y="10" width="95" height="60" />
+
+ <!-- Draw the text string twice. First, the white text without mask.
+ Second, the black text with the mask applied-->
+ <use xlink:href="#Text" fill="white"/>
+ <use xlink:href="#Text" fill="black" mask="url(#myMask)"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/masking-path-07-b.svg b/svgio/qa/cppunit/data/masking-path-07-b.svg
new file mode 100644
index 0000000000..eca3660bab
--- /dev/null
+++ b/svgio/qa/cppunit/data/masking-path-07-b.svg
@@ -0,0 +1,147 @@
+<svg version="1.1" baseProfile="basic" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!--======================================================================-->
+ <!--= SVG 1.1 2nd Edition Test Case =-->
+ <!--======================================================================-->
+ <!--= Copyright 2009 World Wide Web Consortium, (Massachusetts =-->
+ <!--= Institute of Technology, European Research Consortium for =-->
+ <!--= Informatics and Mathematics (ERCIM), Keio University). =-->
+ <!--= All Rights Reserved. =-->
+ <!--= See http://www.w3.org/Consortium/Legal/. =-->
+ <!--======================================================================-->
+ <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/"
+ template-version="1.3" reviewer="CM" author="ED" status="accepted"
+ version="$Revision: 1.11 $" testname="$RCSfile: masking-path-07-b.svg,v $">
+ <d:testDescription xmlns="http://www.w3.org/1999/xhtml" href="http://www.w3.org/TR/SVG11/masking.html#ClippingPaths">
+ <p>
+ This tests that 'clipPath' elements can be used together and how the clipping paths are intersected.
+ </p>
+ <p>
+ There is a gray-white pattern as a background for the two subtest rectangles. This is to show that the holes that are cut out using clip-paths are transparent.
+ The first subtest verifies that when you use the 'clip-path' property on a child element inside a 'clipPath' element the child element is clipped correctly.
+ The second subtest verifies that when a 'clipPath' element has a 'clip-path' property the result is the intersection of the two clip paths.
+ </p>
+ </d:testDescription>
+ <d:operatorScript xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Run the test. No interaction required.
+ </p>
+ </d:operatorScript>
+ <d:passCriteria xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The test has passed if the following conditions are met:
+ </p>
+ <ul>
+ <li>There is no red visible.</li>
+ <li>No shapes extend outside of the rects that have a thick black border.</li>
+ <li>For the left subtest:
+ <ul>
+ <li>There must be a large blue rect with a transparent smaller rect in it, and the intersection of two circles.</li>
+ <li>The borders of the clipregions are shown with black stroke.</li>
+ <li>The blue shapes must be visible only inside of these stroked regions.</li>
+ </ul>
+ </li>
+ <li>For the right subtest:
+ <ul>
+ <li>The test on the right must show part of the large blue rect shape with a transparent rect in it, and part of a circle.</li>
+ <li>The blue shapes must only be visible inside of the circle that has black stroke.</li>
+ </ul>
+ </li>
+ </ul>
+ </d:passCriteria>
+ </d:SVGTestCase>
+ <title id="test-title">$RCSfile: masking-path-07-b.svg,v $</title>
+ <defs>
+ <font-face font-family="DejaVu Sans" unicode-range="U+0-7F">
+ <font-face-src>
+ <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
+ </font-face-src>
+ </font-face>
+ </defs>
+ <g id="test-body-content" font-family="DejaVu Sans" font-size="18">
+
+ <defs>
+ <clipPath id="clipCircle1">
+ <circle id="c1" cx="100" cy="100" r="50"/>
+ </clipPath>
+
+ <clipPath id="clipCircle2">
+ <circle id="c2" cx="150" cy="150" r="50"/>
+ </clipPath>
+
+ <clipPath id="clipPath1">
+ <path id="p1" d="M10 10l100 0 0 100 -100 0ZM50 50l40 0 0 40 -40 0Z" clip-rule="evenodd"/>
+ </clipPath>
+
+ <!-- "If a valid 'clip-path' reference is placed on one of the children of a 'clipPath' element,
+ then the given child element is clipped by the referenced clipping path before OR'ing the
+ silhouette of the child element with the silhouettes of the other child elements." -->
+ <clipPath id="clipRects1">
+ <rect x="50" y="30" width="25" height="100"/>
+ <rect x="25" y="50" width="10" height="10" clip-path="url(#clipTwoCircles)"/>
+ </clipPath>
+
+ <!-- Test use in a clipPath -->
+ <clipPath id="clipTwoCircles">
+ <use xlink:href="#c1"/>
+ <use xlink:href="#c2"/>
+ </clipPath>
+
+ <clipPath id="clipInClip1">
+ <use xlink:href="#c2" clip-path="url(#clipCircle1)"/>
+ <use xlink:href="#p1"/>
+ </clipPath>
+
+ <clipPath id="clipOnClip1" clip-path="url(#clipCircle1)">
+ <use xlink:href="#c2"/>
+ <use xlink:href="#p1"/>
+ </clipPath>
+
+ <pattern patternUnits="userSpaceOnUse" id="pattern" x="0" y="0" width="20" height="20">
+ <rect x="0" y="0" width="10" height="10" fill="gray"/>
+ <rect x="10" y="10" width="10" height="10" fill="gray"/>
+ </pattern>
+ </defs>
+
+ <rect x="20" y="70" width="210" height="210" fill="url(#pattern)" stroke="black" stroke-width="4"/>
+ <rect x="250" y="70" width="210" height="210" fill="url(#pattern)" stroke="black" stroke-width="4"/>
+
+ <text x="240" y="2em" text-anchor="middle">Test clip unions and intersections</text>
+
+ <g transform="translate(20, 70)">
+ <g id="subtest1">
+ <use xlink:href="#p1" fill="red" fill-rule="evenodd"/>
+ <use xlink:href="#c2" fill="red" clip-path="url(#clipCircle1)"/>
+ <use xlink:href="#c1" fill="red" clip-path="url(#clipCircle2)"/>
+
+ <rect width="200" height="200" fill="blue" clip-path="url(#clipInClip1)"/>
+
+ <use xlink:href="#c2" fill="none" clip-path="url(#clipCircle1)" stroke="black"/>
+ <use xlink:href="#c1" fill="none" clip-path="url(#clipCircle2)" stroke="black"/>
+ <use xlink:href="#p1" fill="none" stroke="black"/>
+ </g>
+
+ <g id="subtest2" transform="translate(230,0)">
+ <g clip-path="url(#clipCircle1)">
+ <use xlink:href="#c2" fill="red"/>
+ <use xlink:href="#p1" fill="red" fill-rule="evenodd"/>
+ </g>
+
+ <rect width="300" height="300" fill="blue" clip-path="url(#clipOnClip1)"/>
+
+ <use xlink:href="#c1" fill="none" stroke="black"/>
+ </g>
+ </g>
+ </g>
+ <g font-family="DejaVu Sans" font-size="32">
+ <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.11 $</text>
+ </g>
+ <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
+ <!-- comment out this watermark once the test is approved --><!--
+ <g id="draft-watermark">
+ <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
+ <text font-family="DejaVu Sans" font-weight="bold" font-size="20" x="240"
+ text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
+ </g>-->
+</svg>
diff --git a/svgio/qa/cppunit/data/noneColor.svg b/svgio/qa/cppunit/data/noneColor.svg
new file mode 100644
index 0000000000..552a1cf5af
--- /dev/null
+++ b/svgio/qa/cppunit/data/noneColor.svg
@@ -0,0 +1,3 @@
+<svg width="400" height="110">
+ <rect width="300" height="100" style="fill:none;stroke-width:3;stroke:rgb(0,0,0)" />
+</svg>
diff --git a/svgio/qa/cppunit/data/path.svg b/svgio/qa/cppunit/data/path.svg
new file mode 100644
index 0000000000..559ceec6d5
--- /dev/null
+++ b/svgio/qa/cppunit/data/path.svg
@@ -0,0 +1,3 @@
+<svg width="44" height="26" xmlns="http://www.w3.org/2000/svg">
+ <path d="M1 1 H 43 V 25 H 1 L 1 1 Z" fill="#007aff" stroke="#fff" opacity="0.1" stroke-width="0"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/symbol.svg b/svgio/qa/cppunit/data/symbol.svg
new file mode 100644
index 0000000000..55110f3740
--- /dev/null
+++ b/svgio/qa/cppunit/data/symbol.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<svg width="120" height="120"
+ viewPort="0 0 120 120" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <symbol>
+ <rect x="10" y="10" width="100" height="100" fill="red"/>
+ </symbol>
+
+ <rect x="10" y="10" width="10" height="10" fill="#00D000"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf101237.svg b/svgio/qa/cppunit/data/tdf101237.svg
new file mode 100644
index 0000000000..e5afa37384
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf101237.svg
@@ -0,0 +1,11 @@
+<svg version="1.1" baseProfile="basic" id="svg-root"
+ width="100%" height="100%" viewBox="0 0 480 360"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none">
+
+ <clipPath id="p.0">
+ <path
+ d="m0 0l437.0 0l0 487.0l-437.0 0l0 -487.0z"
+ clip-rule="nonzero"/>
+ </clipPath>
+ <circle clip-path="url(#p.0)" id="c1" cx="100" cy="100" r="50" style="fill:red" stroke-width="5px" stroke="black"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf103888.svg b/svgio/qa/cppunit/data/tdf103888.svg
new file mode 100644
index 0000000000..157bb12571
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf103888.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ width="75mm"
+ height="15mm"
+ viewBox="0 0 250 50">
+ <text
+ style="font-size:30px;font-family:'DejaVu Sans';fill:#000000;stroke:none"
+ x="20"
+ y="30">Her<tspan style="font-weight:bold">vor</tspan>hebung</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf104339.svg b/svgio/qa/cppunit/data/tdf104339.svg
new file mode 100644
index 0000000000..fe55c7bfc1
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf104339.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ id="svg2"
+ version="1.1"
+ xml:space="preserve"
+ width="565.10504"
+ height="132.81749"
+ viewBox="0 0 565.10504 132.8175">
+ <defs>
+ <clipPath clipPathUnits="userSpaceOnUse" id="clipPath16">
+ <path d="M 0,612 792,612 792,0 0,0 0,612 Z"/>
+ </clipPath>
+ </defs>
+ <g transform="matrix(1.25,0,0,-1.25,-221.64057,456.20243)">
+ <g transform="translate(16.855932,5.8347458)">
+ <g clip-path="url(#clipPath16)">
+ <g transform="translate(403.16803,288.82005)">
+ <path
+ d="m 0,0 v -7.675 h -34.756 v 51.656 h 8.19 V 0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf117920.svg b/svgio/qa/cppunit/data/tdf117920.svg
new file mode 100644
index 0000000000..487e0f6cb9
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf117920.svg
@@ -0,0 +1,7 @@
+<svg width="100%" height="100%" viewBox="0 0 100 100"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <switch transform="translate(-18,-6)">
+ <rect width="13" height="13" x="18" y="6" />
+ </switch>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf123926.svg b/svgio/qa/cppunit/data/tdf123926.svg
new file mode 100644
index 0000000000..085b736b53
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf123926.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" class="highcharts-root" style="font-family:&quot;DejaVu Sans&quot;font-size:12px;" width="600" height="400" viewBox="0 0 600 400">
+ <defs>
+ <clipPath id="highcharts-qkip48v-39">
+ <rect x="0" y="0" width="505" height="283" fill="none"/>
+ </clipPath>
+ </defs>
+ <g class="highcharts-series-group" data-z-index="3">
+ <g data-z-index="0.1" class="highcharts-series highcharts-series-0 highcharts-bubble-series highcharts-color-0 " transform="translate(85,61) scale(1 1)" clip-path="url(#highcharts-qkip48v-39)">
+ <path fill="rgb(124,181,236)" fill-opacity="0.5" d="M 501 123.5 A 13.5 13.5 0 1 1 500.99999325000056 123.48650000225 Z" stroke="#7cb5ec" stroke-width="1" class="highcharts-point highcharts-color-0"/>
+ </g>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf129356.svg b/svgio/qa/cppunit/data/tdf129356.svg
new file mode 100644
index 0000000000..46bd6935da
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf129356.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 800 800">
+
+ <style>
+ g.g1 rect {fill:green;}
+ g#g3 rect {fill:green;}
+ g.g4 #r1 {fill:green;}
+ g#g3 .r5 {fill:green;}
+ </style>
+
+ <g class="g4 g1">
+ <g class="g2">
+ <rect x="0" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="60" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect x="120" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="180" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g class="g4 g1">
+ <g>
+ <rect id="r1" x="240" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect id="r1" x="300" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect class="r5" x="360" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect class="r5" x="420" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf145896.svg b/svgio/qa/cppunit/data/tdf145896.svg
new file mode 100644
index 0000000000..d434a961b9
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf145896.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 300 300">
+
+ <style id="style2">
+ .st0{fill:yellow;}.st1{fill:green;}
+ </style>
+ <style type="text/some-unknown-styling-language">
+ .st2{fill:red;}
+ </style>
+ <rect x="0" y="0" height="50" width="50" class="st0" fill="blue"></rect>
+ <rect x="60" y="0" height="50" width="50" class="st1" fill="blue"></rect>
+ <rect x="120" y="0" height="50" width="50" class="st2" fill="blue"></rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf149673.svg b/svgio/qa/cppunit/data/tdf149673.svg
new file mode 100644
index 0000000000..f73b9959d3
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf149673.svg
@@ -0,0 +1,7 @@
+<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <g id="g1" opacity=".1">
+ <circle id="circle1" cx="100" cy="60" fill="#f00" r="40"/>
+ <circle id="circle2" cx="70" cy="100" fill="#0f0" r="40"/>
+ <circle id="circle3" cx="130" cy="100" fill="#00f" r="40"/>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf149880.svg b/svgio/qa/cppunit/data/tdf149880.svg
new file mode 100644
index 0000000000..08ba748487
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf149880.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <defs>
+ <pattern id="Pattern" x=".05" y=".05" width=".25" height=".25">
+ <rect x="0" y="0" width="50" height="50" fill="skyblue"/>
+ </pattern>
+
+ </defs>
+
+ <rect fill='url("#Pattern")' stroke="black" x="0" y="0" width="200" height="200"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf149893.svg b/svgio/qa/cppunit/data/tdf149893.svg
new file mode 100644
index 0000000000..b6b241566d
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf149893.svg
@@ -0,0 +1,3 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="100%" height="100%" fill=" GREEN"></rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf150124.svg b/svgio/qa/cppunit/data/tdf150124.svg
new file mode 100644
index 0000000000..29b2a1e3fd
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf150124.svg
@@ -0,0 +1,12 @@
+<svg id="svg-root" width="100%" height="100%"
+ viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <invented>
+ <rect id='r1' x='0' y='0' width='50' height='50' fill='red'/>
+ <g>
+ <rect id='r2' x='60' y='0' width='50' height='50' fill='green'/>
+ </g>
+ <circle id="myCircle" cx="50" cy="50" r="40" stroke="blue" />
+ </invented>
+ <use href="myCircle" x="20" fill="white" stroke="red" />
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf151103.svg b/svgio/qa/cppunit/data/tdf151103.svg
new file mode 100644
index 0000000000..664253f8df
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf151103.svg
@@ -0,0 +1,18 @@
+<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
+ <text text-anchor="start" x="60" y="40">ABC</text>
+ <text text-anchor="middle" x="60" y="50">ABC</text>
+ <text text-anchor="end" x="60" y="60">ABC</text>
+
+ <text><tspan text-anchor="start" x="60" y="40">ABC</tspan></text>
+ <text><tspan text-anchor="middle" x="60" y="50">ABC</tspan></text>
+ <text><tspan text-anchor="end" x="60" y="60">ABC</tspan></text>
+
+ <text text-anchor="start" x="60" y="40"><tspan>ABC</tspan></text>
+ <text text-anchor="middle" x="60" y="50"><tspan>ABC</tspan></text>
+ <text text-anchor="end" x="60" y="60"><tspan>ABC</tspan></text>
+
+ <text text-anchor="start" x="60" y="40">A<tspan>B</tspan>C</text>
+ <text text-anchor="middle" x="60" y="50">A<tspan>B</tspan>C</text>
+ <text text-anchor="end" x="60" y="60">A<tspan>B</tspan>C</text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf155733.svg b/svgio/qa/cppunit/data/tdf155733.svg
new file mode 100644
index 0000000000..db04ba9afd
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf155733.svg
@@ -0,0 +1,20 @@
+<svg
+ width="100%"
+ height="100%"
+ viewBox="0 0 150 500"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <g id="circles">
+ <circle cx="30" cy="30" r="20" fill="blue" fill-opacity="0.5" />
+ </g>
+ </defs>
+
+ <filter id="myFilter">
+ <feGaussianBlur in="SourceGraphic" stdDeviation="5"/>
+ </filter>
+
+ <use href="#circles" transform="translate(0 50)" filter="url(#myFilter)" />
+ <use href="#circles" transform="translate(0 100)"/>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf155814.svg b/svgio/qa/cppunit/data/tdf155814.svg
new file mode 100644
index 0000000000..5ac2e82973
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf155814.svg
@@ -0,0 +1,25 @@
+<svg
+ width="100%"
+ height="100%"
+ viewBox="0 0 150 500"
+ preserveAspectRatio="xMidYMid meet"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- ref -->
+ <defs>
+ <g id="circles">
+ <circle cx="30" cy="30" r="20" fill="blue" fill-opacity="0.5" />
+ </g>
+ </defs>
+ <clipPath id="myClip">
+ <!--
+ Everything outside the circle will be
+ clipped and therefore invisible.
+ -->
+ <circle r="35" />
+ </clipPath>
+
+ <use xlink:href="#circles" transform="translate(0 50)" clip-path="url(#myClip)" />
+ <use xlink:href="#circles" transform="translate(0 100)" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf155819.svg b/svgio/qa/cppunit/data/tdf155819.svg
new file mode 100644
index 0000000000..30c2da4d1a
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf155819.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+ <style>
+ path {
+ fill: none;
+ stroke-width: 4px;
+ marker: url(#diamond);
+ }
+ </style>
+ <path d="M 10,50 v -20 h 40 v -20" stroke="red"/>
+ <marker id="diamond" markerWidth="12" markerHeight="12" refX="6" refY="6" markerUnits="userSpaceOnUse">
+ <circle cx="6" cy="6" r="3"
+ fill="white" stroke="context-stroke" stroke-width="2"/>
+ </marker>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf155833.svg b/svgio/qa/cppunit/data/tdf155833.svg
new file mode 100644
index 0000000000..8cc908424a
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf155833.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="32.677876mm"
+ height="32.677876mm"
+ viewBox="0 0 32.677876 32.677876"
+ version="1.1"
+ id="svg1126"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <g
+ id="layer1"
+ transform="translate(-80.317197,-107.43993)">
+ <g
+ id="g18033"
+ transform="matrix(0.35277777,0,0,-0.35277777,71.799819,211.06676)">
+ <g
+ id="g18041"
+ transform="matrix(92.88,0,0,92.88,24.14375,201.11516)">
+ <image
+ width="1"
+ height="1"
+ transform="matrix(1,0,0,-1,0,1)"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIEAAACBCAYAAADnoNlQAAAABHNCSVQICAgIfAhkiAAAAUJJREFUeJzt3EENAkEQRcG3aMAjMpYzBgjqSDCyWJhbZ5IqBf/w0sc+6rzaxnN6wLpzesC62/QA5okAESACEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAiojvv12+at7aPP9IRlO211CRABIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAdVzvtnlr22t6wLrnd3rBOpcAESACEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBCQCEgGJgERAIiARkAhIBFR/ALUM7y9VYLsAAAAASUVORK5CYII="
+ id="image18043" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf155932.svg b/svgio/qa/cppunit/data/tdf155932.svg
new file mode 100644
index 0000000000..b533eda3cd
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf155932.svg
@@ -0,0 +1,22 @@
+<svg
+ width="100%"
+ height="100%"
+ viewBox="0 0 150 500"
+ preserveAspectRatio="xMidYMid meet"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- ref -->
+ <defs>
+ <g id="circles">
+ <circle cx="30" cy="30" r="20" fill="blue" fill-opacity="0.5" />
+ </g>
+ </defs>
+ <clipPath id="myClip">
+ <circle r="35" />
+ </clipPath>
+
+ <g clip-path="url(#myClip)">
+ <circle cx="30" cy="30" r="20" fill="blue" fill-opacity="0.5" />
+ </g>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf156018.svg b/svgio/qa/cppunit/data/tdf156018.svg
new file mode 100644
index 0000000000..cff3f924a5
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156018.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 300 300">
+
+ <style id="style2">
+ g rect {fill:green;}
+ </style>
+
+ <g>
+ <rect x="00" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="60" y="0" height="50" width="50" fill="blue"></rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156034.svg b/svgio/qa/cppunit/data/tdf156034.svg
new file mode 100644
index 0000000000..1cd3a82b78
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156034.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 800 800">
+
+ <style>
+ .g1 rect {fill:green;}
+ #g3 rect {fill:green;}
+ .g4 #r1 {fill:green;}
+ #g3 .r5 {fill:green;}
+ </style>
+
+ <g class="g4 g1">
+ <g class="g2">
+ <rect x="0" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="60" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect x="120" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="180" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g class="g4 g1">
+ <g>
+ <rect id="r1" x="240" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect id="r1" x="300" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect class="r5" x="360" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect class="r5" x="420" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156038.svg b/svgio/qa/cppunit/data/tdf156038.svg
new file mode 100644
index 0000000000..9835453e7f
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156038.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 800 800">
+
+ <style>
+ .g1 > rect {fill:green;}
+ #g3 > rect {fill:green;}
+ .g4 > #r1 {fill:green;}
+ #g3 > .r5 {fill:green;}
+ </style>
+
+ <g class="g4 g1">
+ <g class="g2">
+ <rect x="0" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="60" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect x="120" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect x="180" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g class="g4 g1">
+ <g>
+ <rect id="r1" x="240" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect id="r1" x="300" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <g id="g3">
+ <g id="g4">
+ <rect class="r5" x="360" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+ <rect class="r5" x="420" y="0" height="50" width="50" fill="blue"></rect>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156167.svg b/svgio/qa/cppunit/data/tdf156167.svg
new file mode 100644
index 0000000000..034f86563d
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156167.svg
@@ -0,0 +1,18 @@
+<svg id="svg-root" width="100%" height="100%"
+ viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="test-body-content" font-family="DejaVu Sans" font-size="18">
+
+ <g fill="orange">
+ <circle id="a" FiLl="red" cx="140" cy="100" r="50"/>
+ </g>
+ <circle id="b" fill="red" style="FiLl: oRaNgE" cx="340" cy="100" r="50"/>
+ <circle id="c" fill="blue" cx="140" cy="220" r="50"/>
+
+ <style type="text/css">
+ #c {fill: red }
+ #c {FiLl: oRaNgE }
+ </style>
+
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156168.svg b/svgio/qa/cppunit/data/tdf156168.svg
new file mode 100644
index 0000000000..352de2be31
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156168.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg version="1.2" baseProfile="tiny"
+ width="100%" height="100%" viewBox="0 0 200 500"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ style="font-family: 'DejaVu Serif';
+ font-size:large" >
+<title>stroke enabled</title>
+
+ <style type="text/css">
+ #MyRed {
+ fill: red;
+ }
+ #MyBlue {
+ fill: blue;
+ }
+ .MyLime {
+ stroke: lime;
+ stroke-width: 5;
+ }
+ </style>
+
+<g id="MyBlue">
+<rect x="10" y="0" height="50" width="50"></rect>
+<rect x="10" y="60" height="50" width="50" class="MyLime"></rect>
+<rect id="MyRed" x="10" y="120" height="50" width="50"></rect>
+<rect id="MyRed" x="10" y="180" height="50" width="50" class="MyLime"></rect>
+</g>
+<rect x="10" y="240" height="50" width="50"></rect>
+<rect x="10" y="300" height="50" width="50" class="MyLime"></rect>
+<rect id="MyRed" x="10" y="360" height="50" width="50"></rect>
+<rect id="MyRed" x="10" y="420" height="50" width="50" class="MyLime"></rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156201.svg b/svgio/qa/cppunit/data/tdf156201.svg
new file mode 100644
index 0000000000..3000e17f45
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156201.svg
@@ -0,0 +1,36 @@
+<svg style="width: 100%; height: 100%;" height="492pt" version="1.1" viewBox="0 0 500 492" width="500pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="PathCollection_1">
+ <defs>
+ <path d="
+M52.0418 -266.565
+L54.1695 -266.795
+L54.1955 -266.832
+L54.2469 -266.844
+L56.3492 -268.627
+L57.4099 -268.849
+L59.524 -270.389
+L60.4641 -271.625
+L62.5433 -273.716
+L62.8642 -275.66
+L63.198 -277.417
+L62.6856 -278.465
+L60.8035 -281.602
+L60.4975 -281.91
+L59.9992 -282.386
+L57.6251 -284.681
+L56.2525 -285.174
+L54.6591 -286.742
+L54.5155 -286.797
+L52.0418 -279.184
+L52.0418 -270.892" id="C0_0_32ce96eee2"></path>
+ </defs>
+ <g clip-path="url(#p65124e5d4c)">
+ <use style="fill:#2f3ba1;" x="0.0" xlink:href="#C0_0_32ce96eee2" y="492.3555"></use>
+ </g>
+ </g>
+ <defs>
+ <clipPath id="p65124e5d4c">
+ <rect height="446.4" width="446.4" x="38.27125" y="23.14175"></rect>
+ </clipPath>
+ </defs>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156236.svg b/svgio/qa/cppunit/data/tdf156236.svg
new file mode 100644
index 0000000000..12268652c0
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156236.svg
@@ -0,0 +1,7 @@
+<svg viewBox="0 0 500 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="20" y="120" width="60" height="60" rx="0" ry="15" />
+ <rect x="120" y="120" width="60" height="60" ry="15" />
+ <rect x="220" y="120" width="60" height="60" rx="-150" ry="15" />
+ <rect x="320" y="120" width="60" height="60" rx="150" ry="15" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf156251.svg b/svgio/qa/cppunit/data/tdf156251.svg
new file mode 100644
index 0000000000..201a0aa69e
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156251.svg
@@ -0,0 +1,17 @@
+<svg viewBox="0 0 240 70" xmlns="http://www.w3.org/2000/svg">
+ <style>
+ tspan {
+ fill: red;
+ }
+ </style>
+
+ <text x="10" y="30" class="small">
+ You are
+ <tspan>not</tspan>
+ a banana!
+ </text>
+ <text x="10" y="60" class="small">
+ You are<tspan> not </tspan>a banana!
+ </text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf156269.svg b/svgio/qa/cppunit/data/tdf156269.svg
new file mode 100644
index 0000000000..e840b351d1
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156269.svg
@@ -0,0 +1,8 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <!-- Some reference text -->
+ <text x="10%" y="50%" fill="grey">one</text>
+
+ <!-- The same text with a shift -->
+ <text dx="50%" dy="50%" x="10%" y="50%">two</text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf156271.svg b/svgio/qa/cppunit/data/tdf156271.svg
new file mode 100644
index 0000000000..ff0267f35c
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156271.svg
@@ -0,0 +1,6 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <text x="40 10" y="10">AB</text>
+ <text x="10 10" dx="30" y="20">AB</text>
+ <text x="0 0" dx="40 10" y="30">AB</text>
+ <text x="10" dx="30 0" y="40">AB</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156283.svg b/svgio/qa/cppunit/data/tdf156283.svg
new file mode 100644
index 0000000000..ee8c46ef5c
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156283.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <text x="30 71" y="20">ABC</text>
+ <text x="0" dx="30 29" y="30">ABC</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156569.svg b/svgio/qa/cppunit/data/tdf156569.svg
new file mode 100644
index 0000000000..ea9b3f513a
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156569.svg
@@ -0,0 +1,4 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <text x="0,40,80" y="20%">ABC</text>
+ <text x="0 40% 80%" y="30%">ABC</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156577.svg b/svgio/qa/cppunit/data/tdf156577.svg
new file mode 100644
index 0000000000..de12f36667
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156577.svg
@@ -0,0 +1,8 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <text x="30" y="20">
+ <tspan x="30" y="20">ABC</tspan>
+ </text>
+ <text x="30" y="30">
+ <tspan x="30" y="30" dx="0 10 20 30 40">ABC</tspan>
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156616.svg b/svgio/qa/cppunit/data/tdf156616.svg
new file mode 100644
index 0000000000..6b3bb3c6c6
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156616.svg
@@ -0,0 +1,29 @@
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+ <text
+ style="text-anchor:start"
+ x="114"
+ y="103"><tspan
+ x="114"
+ y="103"><tspan>First </tspan>line </tspan><tspan
+ x="114"
+ y="122">Second line</tspan>
+ </text>
+ <text
+ style="text-anchor:middle"
+ x="114"
+ y="153"><tspan
+ x="114"
+ y="153"><tspan>First </tspan>line </tspan><tspan
+ x="114"
+ y="172">Second line</tspan>
+ </text>
+ <text
+ style="text-anchor:end"
+ x="114"
+ y="203"><tspan
+ x="114"
+ y="203"><tspan>First </tspan>line </tspan><tspan
+ x="114"
+ y="222">Second line</tspan>
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156777.svg b/svgio/qa/cppunit/data/tdf156777.svg
new file mode 100644
index 0000000000..9ce1dd8cd3
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156777.svg
@@ -0,0 +1,14 @@
+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <path
+ id="path1"
+ fill="none"
+ stroke="red"
+ d="M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50" />
+
+ <text>
+ <textPath href="#path1" startOffset="40%" style="fill:green">
+ Quick brown fox jumps over the lazy dog.
+ </textPath>
+ </text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf156834.svg b/svgio/qa/cppunit/data/tdf156834.svg
new file mode 100644
index 0000000000..74dc154818
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156834.svg
@@ -0,0 +1,7 @@
+<svg viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20,20 L180,20 M20,50 L180,50 M20,80 L180,80" stroke="grey" />
+
+ <text dominant-baseline="auto" x="30" y="20" font-size="20">Auto</text>
+ <text dominant-baseline="middle" x="30" y="50" font-size="20">Middle</text>
+ <text dominant-baseline="Hanging" x="30" y="80" font-size="20">Hanging</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf156837.svg b/svgio/qa/cppunit/data/tdf156837.svg
new file mode 100644
index 0000000000..f04cb015d3
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf156837.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+
+<defs><style type="text/css">
+.super{
+ font-size: 65%;
+}
+</style></defs>
+
+ <text>
+ <tspan x="114" y="103">x </tspan>
+ <tspan class="super" baseline-shift="super">3</tspan>
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf45771.svg b/svgio/qa/cppunit/data/tdf45771.svg
new file mode 100644
index 0000000000..f49e0f5691
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf45771.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="600" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<text x="5" y="100" font-size="2em">Sample</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf79163.svg b/svgio/qa/cppunit/data/tdf79163.svg
new file mode 100644
index 0000000000..0153037236
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf79163.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <rect x="50" y="50" height="110" width="110"
+ style="fill: #ccccff" opacity="0.5"
+ >
+ </rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf85770.svg b/svgio/qa/cppunit/data/tdf85770.svg
new file mode 100644
index 0000000000..b1cf9eb3c2
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf85770.svg
@@ -0,0 +1,7 @@
+<svg id="svg-root"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="13cm" height="11cm">
+<text x="5" y="35" font-size="x-small">Start Middle End</text>
+<text x="5" y="65" font-size="x-small">Start <tspan visibility="hidden">Middle</tspan> End</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf86938.svg b/svgio/qa/cppunit/data/tdf86938.svg
new file mode 100644
index 0000000000..40287a39de
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf86938.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+ <text
+ x="290"
+ y="183"> line </text>
+ <text
+ x="290"
+ y="183"
+ baseline-shift="24"> above </text>
+ <text
+ x="290"
+ y="183"
+ baseline-shift="-24"> below </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf87309.svg b/svgio/qa/cppunit/data/tdf87309.svg
new file mode 100644
index 0000000000..af8a7df254
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf87309.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" fill="currentColor"/>
+</svg> \ No newline at end of file
diff --git a/svgio/qa/cppunit/data/tdf93583.svg b/svgio/qa/cppunit/data/tdf93583.svg
new file mode 100644
index 0000000000..13e63e677f
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf93583.svg
@@ -0,0 +1,7 @@
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+ <text
+ style="text-anchor:end"
+ x="214"
+ y="303"><tspan>This is the<tspan style="font-size:200%"> first </tspan>line</tspan>
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf94765.svg b/svgio/qa/cppunit/data/tdf94765.svg
new file mode 100644
index 0000000000..009bfc8ed1
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf94765.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="841.9px" height="595.3px" viewBox="0 0 841.9 595.3" style="enable-background:new 0 0 841.9 595.3;" xml:space="preserve"
+ >
+<style type="text/css">
+ .st1{fill:url(#SVGID_1_);stroke:#000000;stroke-miterlimit:10;}
+</style>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="432.1587" y1="340.3492" x2="770.254" y2="340.3492">
+ <stop offset="0" style="stop-color:#DBDBDB"/>
+ <stop offset="1" style="stop-color:#737373"/>
+</linearGradient>
+<rect x="70.3" y="48.3" fill="url(#SVGID_1_)" width="338.1" height="252.4"/>
+<rect x="432.2" y="214.2" class="st1" width="338.1" height="252.4"/>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf95400.svg b/svgio/qa/cppunit/data/tdf95400.svg
new file mode 100644
index 0000000000..378100a212
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf95400.svg
@@ -0,0 +1,8 @@
+<svg viewBox="0 0 150 100" xmlns="http://www.w3.org/2000/svg">
+ <text x="30" y="20" textLength="102" lengthAdjust="spacing">
+ ABC
+ </text>
+ <text x="30" y="30" textLength="102" lengthAdjust="spacingAndGlyphs">
+ ABC
+ </text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97542_1.svg b/svgio/qa/cppunit/data/tdf97542_1.svg
new file mode 100644
index 0000000000..7ade92dd94
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97542_1.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<svg width="200" height="50" xmlns="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <defs>
+ <radialGradient id="svg_2" cx="0.5" cy="0.5" r="0.5">
+ <stop offset="0" stop-color="#ff0000"/>
+ <stop offset="1" stop-color="#000000"/>
+ </radialGradient>
+ </defs>
+ <g fill="url(#svg_2)" >
+ <title>Layer 1</title>
+ <rect id="svg_1" height="50" width="200" y="0" x="0"/>
+ <text x="50" y="40" id="svg_12" font-size="48" font-family="DejaVu Serif" fill="#ffff00">Text</text>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97542_2.svg b/svgio/qa/cppunit/data/tdf97542_2.svg
new file mode 100644
index 0000000000..4a4c7e1276
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97542_2.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<svg width="200" height="50" xmlns="http://www.w3.org/2000/svg">
+ <!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
+ <defs>
+ <radialGradient id="svg_2" cx="1" cy="1" r="3">
+ <stop offset="0" stop-color="#ff0000"/>
+ <stop offset="1" stop-color="#000000"/>
+ </radialGradient>
+ </defs>
+ <g fill="#ffff00">
+ <title>Layer 1</title>
+ <rect id="svg_1" height="50" width="200" y="0" x="0" />
+ <text x="50" y="40" id="svg_12" font-size="48" font-family="DejaVu Serif" fill="url(#svg_2)">Text</text>
+ </g>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97543.svg b/svgio/qa/cppunit/data/tdf97543.svg
new file mode 100644
index 0000000000..fa30b22447
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97543.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect x="10" y="10" width="100" height="100" rx="10" ry="10" fill="#00cc00" visibility="inherit" />
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97710.svg b/svgio/qa/cppunit/data/tdf97710.svg
new file mode 100644
index 0000000000..bbd9a91a9b
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97710.svg
@@ -0,0 +1,8 @@
+<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
+ <!-- Example of a polyline with the default fill -->
+ <polyline points="0,100 50,25 50,75 100,0" />
+
+ <!-- Example of the same polyline shape with stroke and no fill -->
+ <polyline points="100,100 150,25 150,75 200,0" fill="green" stroke="black" />
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tdf97717.svg b/svgio/qa/cppunit/data/tdf97717.svg
new file mode 100644
index 0000000000..c354e44168
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97717.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0 0 300 300">
+
+ <g opacity="0.5">
+ <rect x="0" y="0" height="50" width="50"
+ style="fill: #ccccff">
+ </rect>
+ </g>
+ <rect x="60" y="0" height="50" width="50"
+ style="fill: #ccccff" opacity="0.5">
+ </rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97936.svg b/svgio/qa/cppunit/data/tdf97936.svg
new file mode 100644
index 0000000000..6c059ec5db
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97936.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect x="70" y="50" height="50" width="50" style="fill: #ccccff"></rect>
+<rect x="10" y="50" height="50" width="50" style="fill: #ccccff"></rect>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf97941.svg b/svgio/qa/cppunit/data/tdf97941.svg
new file mode 100644
index 0000000000..cfe1ca8c47
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf97941.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="600" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<text x="5" y="100">
+ <tspan font-size="3em">Sample</tspan></text>
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf99115.svg b/svgio/qa/cppunit/data/tdf99115.svg
new file mode 100644
index 0000000000..6d4b5e9c1b
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf99115.svg
@@ -0,0 +1,40 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200 px" height="100 px">
+<style type="text/css" id="style1">
+* {font-size:18px;}
+#tspan6 {fill:blue}
+.c1 {fill:green}
+.c2 {fill:red}
+#tspan8 {fill:green}
+#text9 {fill:green}
+</style>
+<text id="text1" style="" x="1" y="20" >
+ <tspan id="tspan1" fill="red">red 1</tspan>
+</text>
+<text id="text2" style="fill:red" x="1" y="40" >
+ <tspan id="tspan2">red 2</tspan>
+</text>
+<text id="text3" style="" x="1" y="60" >
+ <tspan id="tspan3" style="fill:red">red 3</tspan>
+</text>
+
+<text id="text4" style="fill:red" x="60" y="20" >
+ <tspan id="tspan4" fill="blue">blue 4</tspan>
+</text>
+<text id="text5" x="60" y="40" >
+ <tspan id="tspan5" style="fill:blue" fill="red">blue 5</tspan>
+</text>
+<text id="text6" style="fill:red" x="60" y="60" >
+ <tspan id="tspan6">blue 6</tspan>
+</text>
+
+<text id="text7" x="130" y="20" >
+ <tspan id="tspan7" class="c1" fill="blue">green 7</tspan>
+</text>
+<text id="text8" x="130" y="40" >
+ <tspan id="tspan8" class="c2" fill="red">green 8</tspan>
+</text>
+<text id="text9" x="130" y="60" class="c2">
+ <tspan id="tspan9">green 9</tspan>
+</text>
+
+</svg>
diff --git a/svgio/qa/cppunit/data/tdf99994.svg b/svgio/qa/cppunit/data/tdf99994.svg
new file mode 100644
index 0000000000..b948338c14
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf99994.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<style type="text/css" id="style1">
+*{fill:blue;}
+</style>
+<text id="text1" style="font-family:DejaVu Sans;">
+ <tspan id="tspan1">test</tspan>
+</text>
+</svg>
diff --git a/svgio/qa/cppunit/data/textXmlSpace.svg b/svgio/qa/cppunit/data/textXmlSpace.svg
new file mode 100644
index 0000000000..fe1bc8ceeb
--- /dev/null
+++ b/svgio/qa/cppunit/data/textXmlSpace.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ viewBox="0 0 250 250">
+ <text y="10" xml:space="default"> a b </text>
+ <text y="30" xml:space="default">a b</text>
+ <text y="50" xml:space="default">a
+ b</text>
+ <text y="70" xml:space="default">a
+b</text>
+ <text y="90" xml:space="preserve"> a b </text>
+ <text y="110" xml:space="preserve">a b</text>
+ <text y="130" xml:space="preserve">a
+ b</text>
+ <text y="150" xml:space="preserve">a
+b</text>
+</svg>
+
diff --git a/svgio/qa/cppunit/data/tspan-fill-opacity.svg b/svgio/qa/cppunit/data/tspan-fill-opacity.svg
new file mode 100644
index 0000000000..0fe6a1cd8d
--- /dev/null
+++ b/svgio/qa/cppunit/data/tspan-fill-opacity.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.2" width="210mm" height="297mm" viewBox="0 0 21000 29700" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xml:space="preserve">
+ <g class="ClosedBezierShape">
+ <rect stroke="none" fill="none" x="9737" y="6537" width="7527" height="3527"/>
+ <g style="opacity: 0.30">
+ <path fill="none" stroke="rgb(255,0,0)" stroke-width="25" stroke-linejoin="round" d="M 9875,6550 C 9806,6550 9750,6606 9750,6675 L 9750,9925 C 9750,9994 9806,10050 9875,10050 L 17125,10050 C 17194,10050 17250,9994 17250,9925 L 17250,6675 C 17250,6606 17194,6550 17125,6550 L 17000,6550 9875,6550 Z"/>
+ </g>
+ </g>
+ <g class="TextShape">
+ <rect stroke="none" fill="none" x="9825" y="6550" width="4076" height="955"/>
+ <text>
+ <tspan x="9825" y="7939" font-family="DejaVu Sans" font-size="800px" fill-opacity="0.30" fill="rgb(255,0,0)" stroke="none">hello</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/svgio/source/svgreader/SvgNumber.cxx b/svgio/source/svgreader/SvgNumber.cxx
new file mode 100644
index 0000000000..c1558f3e64
--- /dev/null
+++ b/svgio/source/svgreader/SvgNumber.cxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <SvgNumber.hxx>
+
+#include <o3tl/unit_conversion.hxx>
+#include <sal/log.hxx>
+
+namespace svgio::svgreader
+{
+
+double SvgNumber::solveNonPercentage(const InfoProvider& rInfoProvider) const
+{
+ if (!isSet())
+ {
+ SAL_WARN("svgio", "SvgNumber not set (!)");
+ return 0.0;
+ }
+
+ switch (meUnit)
+ {
+ case SvgUnit::em:
+ return mfNumber * rInfoProvider.getCurrentFontSizeInherited();
+ case SvgUnit::ex:
+ return mfNumber * rInfoProvider.getCurrentXHeightInherited() * 0.5;
+ case SvgUnit::px:
+ return mfNumber;
+ case SvgUnit::pt:
+ return o3tl::convert(mfNumber, o3tl::Length::pt, o3tl::Length::px);
+ case SvgUnit::pc:
+ return o3tl::convert(mfNumber, o3tl::Length::pc, o3tl::Length::px);
+ case SvgUnit::cm:
+ return o3tl::convert(mfNumber, o3tl::Length::cm, o3tl::Length::px);
+ case SvgUnit::mm:
+ return o3tl::convert(mfNumber, o3tl::Length::mm, o3tl::Length::px);
+ case SvgUnit::in:
+ return o3tl::convert(mfNumber, o3tl::Length::in, o3tl::Length::px);
+ case SvgUnit::none:
+ {
+ SAL_WARN("svgio", "Design error, this case should have been handled in the caller");
+ return mfNumber;
+ }
+ case SvgUnit::percent:
+ {
+ SAL_WARN("svgio", "Do not use with percentage!");
+ break;
+ }
+ }
+
+ return 0.0;
+}
+
+double SvgNumber::solve(const InfoProvider& rInfoProvider, NumberType aNumberType) const
+{
+ if (!isSet())
+ {
+ SAL_WARN("svgio", "SvgNumber not set (!)");
+ return 0.0;
+ }
+
+ if (meUnit == SvgUnit::percent)
+ {
+ double fRetval(mfNumber * 0.01);
+ basegfx::B2DRange aViewPort = rInfoProvider.getCurrentViewPort();
+
+ if ( aViewPort.isEmpty() )
+ {
+ SAL_WARN("svgio", "Design error, this case should have been handled in the caller");
+ // no viewPort, assume a normal page size (A4)
+ aViewPort = basegfx::B2DRange(
+ 0.0,
+ 0.0,
+ o3tl::convert(210.0, o3tl::Length::cm, o3tl::Length::px), // should it be mm?
+ o3tl::convert(297.0, o3tl::Length::cm, o3tl::Length::px));
+
+ }
+
+ if ( !aViewPort.isEmpty() )
+ {
+ if (NumberType::xcoordinate == aNumberType)
+ {
+ // it's a x-coordinate, relative to current width (w)
+ fRetval *= aViewPort.getWidth();
+ }
+ else if (NumberType::ycoordinate == aNumberType)
+ {
+ // it's a y-coordinate, relative to current height (h)
+ fRetval *= aViewPort.getHeight();
+ }
+ else // length
+ {
+ // it's a length, relative to sqrt((w^2 + h^2)/2)
+ const double fCurrentWidth(aViewPort.getWidth());
+ const double fCurrentHeight(aViewPort.getHeight());
+ const double fCurrentLength(
+ sqrt((fCurrentWidth * fCurrentWidth + fCurrentHeight * fCurrentHeight)/2.0));
+
+ fRetval *= fCurrentLength;
+ }
+ }
+
+ return fRetval;
+ }
+
+ return solveNonPercentage( rInfoProvider);
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svganode.cxx b/svgio/source/svgreader/svganode.cxx
new file mode 100644
index 0000000000..67b523b7ef
--- /dev/null
+++ b/svgio/source/svgreader/svganode.cxx
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svganode.hxx>
+
+namespace svgio::svgreader
+{
+ SvgANode::SvgANode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::A, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgANode::~SvgANode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgANode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgANode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ //TODO: add support for xlink:href
+ break;
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgANode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ // #i125258# for SVGTokenA decompose children
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!pStyle)
+ return;
+
+ if (Display::None != getDisplay())
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aContent;
+
+ // decompose children
+ SvgNode::decomposeSvgNode(aContent, bReferenced);
+
+ if(!aContent.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aContent), getTransform());
+ }
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgcharacternode.cxx b/svgio/source/svgreader/svgcharacternode.cxx
new file mode 100644
index 0000000000..7d5a2fcb9f
--- /dev/null
+++ b/svgio/source/svgreader/svgcharacternode.cxx
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgcharacternode.hxx>
+#include <svgstyleattributes.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
+#include <drawinglayer/primitive2d/textbreakuphelper.hxx>
+#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <utility>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+using namespace drawinglayer::primitive2d;
+
+namespace svgio::svgreader
+{
+ namespace {
+
+ class localTextBreakupHelper : public TextBreakupHelper
+ {
+ private:
+ SvgTextPosition& mrSvgTextPosition;
+
+ protected:
+ /// allow user callback to allow changes to the new TextTransformation. Default
+ /// does nothing.
+ virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
+
+ public:
+ localTextBreakupHelper(
+ const TextSimplePortionPrimitive2D& rSource,
+ SvgTextPosition& rSvgTextPosition)
+ : TextBreakupHelper(rSource),
+ mrSvgTextPosition(rSvgTextPosition)
+ {
+ }
+ };
+
+ }
+
+ bool localTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
+ {
+ const double fRotation(mrSvgTextPosition.consumeRotation());
+
+ if(0.0 != fRotation)
+ {
+ const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
+
+ rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
+ rNewTransform.rotate(fRotation);
+ rNewTransform.translate(aBasePoint.getX(), aBasePoint.getY());
+ }
+
+ return true;
+ }
+
+ SvgCharacterNode::SvgCharacterNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent,
+ OUString aText)
+ : SvgNode(SVGToken::Character, rDocument, pParent),
+ maText(std::move(aText)),
+ mpParentLine(nullptr)
+ {
+ }
+
+ SvgCharacterNode::~SvgCharacterNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgCharacterNode::getSvgStyleAttributes() const
+ {
+ // no own style, use parent's
+ if(getParent())
+ {
+ return getParent()->getSvgStyleAttributes();
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+
+ drawinglayer::attribute::FontAttribute SvgCharacterNode::getFontAttribute(
+ const SvgStyleAttributes& rSvgStyleAttributes)
+ {
+ const SvgStringVector& rFontFamilyVector = rSvgStyleAttributes.getFontFamily();
+ OUString aFontFamily("Times New Roman");
+ if(!rFontFamilyVector.empty())
+ aFontFamily=rFontFamilyVector[0];
+
+ // #i122324# if the FontFamily name ends on ' embedded' it is probably a re-import
+ // of a SVG export with font embedding. Remove this to make font matching work. This
+ // is pretty safe since there should be no font family names ending on ' embedded'.
+ // Remove again when FontEmbedding is implemented in SVG import
+ if(aFontFamily.endsWith(" embedded"))
+ {
+ aFontFamily = aFontFamily.copy(0, aFontFamily.getLength() - 9);
+ }
+
+ const ::FontWeight nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
+ bool bItalic(FontStyle::italic == rSvgStyleAttributes.getFontStyle() || FontStyle::oblique == rSvgStyleAttributes.getFontStyle());
+
+ return drawinglayer::attribute::FontAttribute(
+ aFontFamily,
+ OUString(),
+ nFontWeight,
+ false/*bSymbol*/,
+ false/*bVertical*/,
+ bItalic,
+ false/*bMonospaced*/,
+ false/*bOutline*/,
+ false/*bRTL*/,
+ false/*bBiDiStrong*/);
+ }
+
+ rtl::Reference<BasePrimitive2D> SvgCharacterNode::createSimpleTextPrimitive(
+ SvgTextPosition& rSvgTextPosition,
+ const SvgStyleAttributes& rSvgStyleAttributes) const
+ {
+ // prepare retval, index and length
+ rtl::Reference<BasePrimitive2D> pRetval;
+ sal_uInt32 nLength(getText().getLength());
+
+ if(nLength)
+ {
+ sal_uInt32 nIndex(0);
+
+ // prepare FontAttribute
+ const drawinglayer::attribute::FontAttribute aFontAttribute(getFontAttribute(rSvgStyleAttributes));
+
+ // prepare FontSizeNumber
+ double fFontWidth(rSvgStyleAttributes.getFontSizeNumber().solve(*this));
+ double fFontHeight(fFontWidth);
+
+ // prepare locale
+ css::lang::Locale aLocale;
+
+ // prepare TextLayouterDevice
+ TextLayouterDevice aTextLayouterDevice;
+ aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
+
+ // prepare TextArray
+ ::std::vector< double > aTextArray(rSvgTextPosition.getX());
+ ::std::vector< double > aDxArray(rSvgTextPosition.getDx());
+
+ // Do nothing when X and Dx arrays are empty
+ if((!aTextArray.empty() || !aDxArray.empty()) && aTextArray.size() < nLength)
+ {
+ const sal_uInt32 nArray(aTextArray.size());
+
+ double fStartX(0.0);
+ if (!aTextArray.empty())
+ {
+ if(rSvgTextPosition.getParent() && rSvgTextPosition.getParent()->getAbsoluteX())
+ {
+ fStartX = rSvgTextPosition.getParent()->getPosition().getX();
+ }
+ else
+ {
+ fStartX = aTextArray[nArray - 1];
+ }
+ }
+
+ ::std::vector< double > aExtendArray(aTextLayouterDevice.getTextArray(getText(), nArray, nLength - nArray));
+ double fComulativeDx(0.0);
+
+ aTextArray.reserve(nLength);
+ for(size_t a = 0; a < aExtendArray.size(); ++a)
+ {
+ if (a < aDxArray.size())
+ {
+ fComulativeDx += aDxArray[a];
+ }
+ aTextArray.push_back(aExtendArray[a] + fStartX + fComulativeDx);
+ }
+ }
+
+ // get current TextPosition and TextWidth in units
+ basegfx::B2DPoint aPosition(rSvgTextPosition.getPosition());
+ double fTextWidth(aTextLayouterDevice.getTextWidth(getText(), nIndex, nLength));
+
+ // check for user-given TextLength
+ if(0.0 != rSvgTextPosition.getTextLength()
+ && !basegfx::fTools::equal(fTextWidth, rSvgTextPosition.getTextLength()))
+ {
+ const double fFactor(rSvgTextPosition.getTextLength() / fTextWidth);
+
+ if(rSvgTextPosition.getLengthAdjust())
+ {
+ // spacing, need to create and expand TextArray
+ if(aTextArray.empty())
+ {
+ aTextArray = aTextLayouterDevice.getTextArray(getText(), nIndex, nLength);
+ }
+
+ for(auto &a : aTextArray)
+ {
+ a *= fFactor;
+ }
+ }
+ else
+ {
+ // spacing and glyphs, just apply to FontWidth
+ fFontWidth *= fFactor;
+ }
+
+ fTextWidth = rSvgTextPosition.getTextLength();
+ }
+
+ // get TextAlign
+ TextAlign aTextAlign(rSvgStyleAttributes.getTextAlign());
+
+ // map TextAnchor to TextAlign, there seems not to be a difference
+ if(TextAnchor::notset != rSvgStyleAttributes.getTextAnchor())
+ {
+ switch(rSvgStyleAttributes.getTextAnchor())
+ {
+ case TextAnchor::start:
+ {
+ aTextAlign = TextAlign::left;
+ break;
+ }
+ case TextAnchor::middle:
+ {
+ aTextAlign = TextAlign::center;
+ break;
+ }
+ case TextAnchor::end:
+ {
+ aTextAlign = TextAlign::right;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ // apply TextAlign
+ switch(aTextAlign)
+ {
+ case TextAlign::right:
+ {
+ aPosition.setX(aPosition.getX() - mpParentLine->getTextLineWidth());
+ break;
+ }
+ case TextAlign::center:
+ {
+ aPosition.setX(aPosition.getX() - (mpParentLine->getTextLineWidth() * 0.5));
+ break;
+ }
+ case TextAlign::notset:
+ case TextAlign::left:
+ case TextAlign::justify:
+ {
+ // TextAlign::notset, TextAlign::left: nothing to do
+ // TextAlign::justify is not clear currently; handle as TextAlign::left
+ break;
+ }
+ }
+
+ // get DominantBaseline
+ const DominantBaseline aDominantBaseline(rSvgStyleAttributes.getDominantBaseline());
+
+ basegfx::B2DRange aRange(aTextLayouterDevice.getTextBoundRect(getText(), nIndex, nLength));
+ // apply DominantBaseline
+ switch(aDominantBaseline)
+ {
+ case DominantBaseline::Middle:
+ {
+ aPosition.setY(aPosition.getY() - aRange.getCenterY());
+ break;
+ }
+ case DominantBaseline::Hanging:
+ {
+ aPosition.setY(aPosition.getY() - aRange.getMinY());
+ break;
+ }
+ default: // DominantBaseline::Auto
+ {
+ // nothing to do
+ break;
+ }
+ }
+
+ // get BaselineShift
+ const BaselineShift aBaselineShift(rSvgStyleAttributes.getBaselineShift());
+
+ // apply BaselineShift
+ switch(aBaselineShift)
+ {
+ case BaselineShift::Sub:
+ {
+ aPosition.setY(aPosition.getY() + aTextLayouterDevice.getUnderlineOffset());
+ break;
+ }
+ case BaselineShift::Super:
+ {
+ aPosition.setY(aPosition.getY() + aTextLayouterDevice.getOverlineOffset());
+ break;
+ }
+ case BaselineShift::Percentage:
+ case BaselineShift::Length:
+ {
+ const SvgNumber aNumber(rSvgStyleAttributes.getBaselineShiftNumber());
+ const double mfBaselineShift(aNumber.solve(*this));
+
+ aPosition.setY(aPosition.getY() - mfBaselineShift);
+ break;
+ }
+ default: // BaselineShift::Baseline
+ {
+ // nothing to do
+ break;
+ }
+ }
+
+ // get fill color
+ basegfx::BColor aFill(0, 0, 0);
+ if(rSvgStyleAttributes.getFill())
+ aFill = *rSvgStyleAttributes.getFill();
+
+ // get fill opacity
+ double fFillOpacity = 1.0;
+ if (rSvgStyleAttributes.getFillOpacity().isSet())
+ {
+ fFillOpacity = rSvgStyleAttributes.getFillOpacity().getNumber();
+ }
+
+ // prepare TextTransformation
+ basegfx::B2DHomMatrix aTextTransform;
+
+ aTextTransform.scale(fFontWidth, fFontHeight);
+ aTextTransform.translate(aPosition.getX(), aPosition.getY());
+
+ // check TextDecoration and if TextDecoratedPortionPrimitive2D is needed
+ const TextDecoration aDeco(rSvgStyleAttributes.getTextDecoration());
+
+ if(TextDecoration::underline == aDeco
+ || TextDecoration::overline == aDeco
+ || TextDecoration::line_through == aDeco)
+ {
+ // get the fill for decoration as described by SVG. We cannot
+ // have different stroke colors/definitions for those, though
+ const SvgStyleAttributes* pDecoDef = rSvgStyleAttributes.getTextDecorationDefiningSvgStyleAttributes();
+
+ basegfx::BColor aDecoColor(aFill);
+ if(pDecoDef && pDecoDef->getFill())
+ aDecoColor = *pDecoDef->getFill();
+
+ TextLine eFontOverline = TEXT_LINE_NONE;
+ if(TextDecoration::overline == aDeco)
+ eFontOverline = TEXT_LINE_SINGLE;
+
+ TextLine eFontUnderline = TEXT_LINE_NONE;
+ if(TextDecoration::underline == aDeco)
+ eFontUnderline = TEXT_LINE_SINGLE;
+
+ TextStrikeout eTextStrikeout = TEXT_STRIKEOUT_NONE;
+ if(TextDecoration::line_through == aDeco)
+ eTextStrikeout = TEXT_STRIKEOUT_SINGLE;
+
+ // create decorated text primitive
+ pRetval = new TextDecoratedPortionPrimitive2D(
+ aTextTransform,
+ getText(),
+ nIndex,
+ nLength,
+ std::move(aTextArray),
+ {},
+ aFontAttribute,
+ aLocale,
+ aFill,
+ COL_TRANSPARENT,
+
+ // extra props for decorated
+ aDecoColor,
+ aDecoColor,
+ eFontOverline,
+ eFontUnderline,
+ false,
+ eTextStrikeout,
+ false,
+ TEXT_FONT_EMPHASIS_MARK_NONE,
+ true,
+ false,
+ TEXT_RELIEF_NONE,
+ false);
+ }
+ else
+ {
+ // create text primitive
+ pRetval = new TextSimplePortionPrimitive2D(
+ aTextTransform,
+ getText(),
+ nIndex,
+ nLength,
+ std::move(aTextArray),
+ {},
+ aFontAttribute,
+ aLocale,
+ aFill);
+ }
+
+ if (fFillOpacity != 1.0)
+ {
+ pRetval = new UnifiedTransparencePrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer{ pRetval },
+ 1.0 - fFillOpacity);
+ }
+
+ // advance current TextPosition
+ rSvgTextPosition.setPosition(rSvgTextPosition.getPosition() + basegfx::B2DVector(fTextWidth, 0.0));
+ }
+
+ return pRetval;
+ }
+
+ void SvgCharacterNode::decomposeTextWithStyle(
+ Primitive2DContainer& rTarget,
+ SvgTextPosition& rSvgTextPosition,
+ const SvgStyleAttributes& rSvgStyleAttributes) const
+ {
+ const Primitive2DReference xRef(
+ createSimpleTextPrimitive(
+ rSvgTextPosition,
+ rSvgStyleAttributes));
+
+ if(!(xRef.is() && (Visibility::visible == rSvgStyleAttributes.getVisibility())))
+ return;
+
+ if(!rSvgTextPosition.isRotated())
+ {
+ rTarget.push_back(xRef);
+ }
+ else
+ {
+ // need to apply rotations to each character as given
+ const TextSimplePortionPrimitive2D* pCandidate =
+ dynamic_cast< const TextSimplePortionPrimitive2D* >(xRef.get());
+
+ if(pCandidate)
+ {
+ localTextBreakupHelper alocalTextBreakupHelper(*pCandidate, rSvgTextPosition);
+ Primitive2DContainer aResult = alocalTextBreakupHelper.extractResult();
+
+ if(!aResult.empty())
+ {
+ rTarget.append(std::move(aResult));
+ }
+
+ // also consume for the implied single space
+ rSvgTextPosition.consumeRotation();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Used primitive is not a text primitive (!)");
+ }
+ }
+ }
+
+ void SvgCharacterNode::whiteSpaceHandling()
+ {
+ bool bIsDefault(XmlSpace::Default == getXmlSpace());
+ // if xml:space="default" then remove all newline characters, otherwise convert them to space
+ // convert tab to space too
+ maText = maTextBeforeSpaceHandling = maText.replaceAll(u"\n", bIsDefault ? u"" : u" ").replaceAll(u"\t", u" ");
+
+ if(bIsDefault)
+ {
+ // strip of all leading and trailing spaces
+ // and consolidate contiguous space
+ maText = consolidateContiguousSpace(maText.trim());
+ }
+ }
+
+ SvgCharacterNode* SvgCharacterNode::addGap(SvgCharacterNode* pPreviousCharacterNode)
+ {
+ // maText may have lost all text. If that's the case, ignore as invalid character node
+ // Also ignore if maTextBeforeSpaceHandling just have spaces
+ if(!maText.isEmpty() && !o3tl::trim(maTextBeforeSpaceHandling).empty())
+ {
+ if(pPreviousCharacterNode)
+ {
+ bool bAddGap(true);
+
+ // Do not add a gap if last node doesn't end with a space and
+ // current note doesn't start with a space
+ const sal_uInt32 nLastLength(pPreviousCharacterNode->maTextBeforeSpaceHandling.getLength());
+ if(pPreviousCharacterNode->maTextBeforeSpaceHandling[nLastLength - 1] != ' ' && maTextBeforeSpaceHandling[0] != ' ')
+ bAddGap = false;
+
+ // Do not add a gap if this node and last node are in different lines
+ if(pPreviousCharacterNode->mpParentLine != mpParentLine)
+ bAddGap = false;
+
+ // add in-between whitespace (single space) to the beginning of the current character node
+ if(bAddGap)
+ {
+ maText = " " + maText;
+ }
+ }
+
+ // this becomes the previous character node
+ return this;
+ }
+
+ return pPreviousCharacterNode;
+ }
+
+ void SvgCharacterNode::concatenate(std::u16string_view rText)
+ {
+ maText += rText;
+ }
+
+ void SvgCharacterNode::decomposeText(Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
+ {
+ if(!getText().isEmpty())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getSvgStyleAttributes();
+
+ if(pSvgStyleAttributes)
+ {
+ decomposeTextWithStyle(rTarget, rSvgTextPosition, *pSvgStyleAttributes);
+ }
+ }
+ }
+
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgcirclenode.cxx b/svgio/source/svgreader/svgcirclenode.cxx
new file mode 100644
index 0000000000..21dac849be
--- /dev/null
+++ b/svgio/source/svgreader/svgcirclenode.cxx
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgcirclenode.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+namespace svgio::svgreader
+{
+ SvgCircleNode::SvgCircleNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Circle, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maCx(0),
+ maCy(0),
+ maR(0)
+ {
+ }
+
+ SvgCircleNode::~SvgCircleNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgCircleNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgCircleNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Cx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCx = aNum;
+ }
+ break;
+ }
+ case SVGToken::Cy:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCy = aNum;
+ }
+ break;
+ }
+ case SVGToken::R:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maR = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgCircleNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!(pStyle && getR().isSet()))
+ return;
+
+ const double fR(getR().solve(*this));
+
+ if(fR <= 0.0)
+ return;
+
+ const basegfx::B2DPolygon aPath(
+ basegfx::utils::createPolygonFromCircle(
+ basegfx::B2DPoint(
+ getCx().isSet() ? getCx().solve(*this, NumberType::xcoordinate) : 0.0,
+ getCy().isSet() ? getCy().solve(*this, NumberType::ycoordinate) : 0.0),
+ fR));
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(basegfx::B2DPolyPolygon(aPath), aNewTarget, nullptr);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgclippathnode.cxx b/svgio/source/svgreader/svgclippathnode.cxx
new file mode 100644
index 0000000000..5941c22da7
--- /dev/null
+++ b/svgio/source/svgreader/svgclippathnode.cxx
@@ -0,0 +1,260 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgclippathnode.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/processor2d/contourextractor2d.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ SvgClipPathNode::SvgClipPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::ClipPathNode, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maClipPathUnits(SvgUnits::userSpaceOnUse)
+ {
+ }
+
+ SvgClipPathNode::~SvgClipPathNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgClipPathNode::getSvgStyleAttributes() const
+ {
+ return &maSvgStyleAttributes;
+ }
+
+ void SvgClipPathNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::ClipPathUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setClipPathUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setClipPathUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgClipPathNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ // decompose children
+ SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
+
+ if(aNewTarget.empty())
+ return;
+
+ if(getTransform())
+ {
+ // create embedding group element with transformation
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ *getTransform(),
+ std::move(aNewTarget)));
+
+ rTarget.push_back(xRef);
+ }
+ else
+ {
+ // append to current target
+ rTarget.append(aNewTarget);
+ }
+ }
+
+ void SvgClipPathNode::apply(
+ drawinglayer::primitive2d::Primitive2DContainer& rContent,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const
+ {
+ if (rContent.empty() || Display::None == getDisplay())
+ return;
+
+ const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
+ drawinglayer::primitive2d::Primitive2DContainer aClipTarget;
+ basegfx::B2DPolyPolygon aClipPolyPolygon;
+
+ // get clipPath definition as primitives
+ decomposeSvgNode(aClipTarget, true);
+
+ if(!aClipTarget.empty())
+ {
+ // extract filled polygons as base for a mask PolyPolygon
+ drawinglayer::processor2d::ContourExtractor2D aExtractor(aViewInformation2D, true);
+
+ aExtractor.process(aClipTarget);
+
+ const basegfx::B2DPolyPolygonVector& rResult(aExtractor.getExtractedContour());
+ const sal_uInt32 nSize(rResult.size());
+
+ if(nSize > 1)
+ {
+ // merge to single clipPolyPolygon
+ aClipPolyPolygon = basegfx::utils::mergeToSinglePolyPolygon(rResult);
+ }
+ else if (nSize != 0)
+ {
+ aClipPolyPolygon = rResult[0];
+ }
+ }
+
+ if(aClipPolyPolygon.count())
+ {
+ if (SvgUnits::objectBoundingBox == getClipPathUnits())
+ {
+ // clip is object-relative, transform using content transformation
+ const basegfx::B2DRange aContentRange(rContent.getB2DRange(aViewInformation2D));
+
+ aClipPolyPolygon.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aContentRange.getRange(),
+ aContentRange.getMinimum()));
+ }
+ else // userSpaceOnUse
+ {
+ // #i124852#
+ if(pTransform)
+ {
+ aClipPolyPolygon.transform(*pTransform);
+ }
+ }
+
+ // #i124313# try to avoid creating an embedding to a MaskPrimitive2D if
+ // possible; MaskPrimitive2D processing is potentially expensive
+ bool bCreateEmbedding(true);
+ bool bAddContent(true);
+
+ if(basegfx::utils::isRectangle(aClipPolyPolygon))
+ {
+ // ClipRegion is a rectangle, thus it is not expensive to tell
+ // if the content is completely inside or outside of it; get ranges
+ const basegfx::B2DRange aClipRange(aClipPolyPolygon.getB2DRange());
+ const basegfx::B2DRange aContentRange(
+ rContent.getB2DRange(
+ aViewInformation2D));
+
+ if(aClipRange.isInside(aContentRange))
+ {
+ // completely contained, no need to clip at all, so no need for embedding
+ bCreateEmbedding = false;
+ }
+ else if(aClipRange.overlaps(aContentRange))
+ {
+ // overlap; embedding needed. ClipRegion can be minimized by using
+ // the intersection of the ClipRange and the ContentRange. Minimizing
+ // the ClipRegion potentially enhances further processing since
+ // usually clip operations are expensive.
+ basegfx::B2DRange aCommonRange(aContentRange);
+
+ aCommonRange.intersect(aClipRange);
+ aClipPolyPolygon = basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aCommonRange));
+ }
+ else
+ {
+ // not inside and no overlap -> completely outside
+ // no need for embedding, no need for content at all
+ bCreateEmbedding = false;
+ bAddContent = false;
+ }
+ }
+ else
+ {
+ // ClipRegion is not a simple rectangle, it would be possible but expensive to
+ // tell if the content needs clipping or not. It is also dependent of
+ // the content's decomposition. To do this, a processor would be needed that
+ // is capable if processing the given sequence of primitives and decide
+ // if all is inside or all is outside. Such a ClipProcessor could be written,
+ // but for now just create the embedding
+ }
+
+ if(bCreateEmbedding)
+ {
+ // redefine target. Use MaskPrimitive2D with created clip
+ // geometry. Using the automatically set mbIsClipPathContent at
+ // SvgStyleAttributes the clip definition is without fill, stroke,
+ // and strokeWidth and forced to black
+ drawinglayer::primitive2d::Primitive2DReference xEmbedTransparence(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ std::move(aClipPolyPolygon),
+ std::move(rContent)));
+
+ rContent = drawinglayer::primitive2d::Primitive2DContainer { xEmbedTransparence };
+ }
+ else
+ {
+ if(!bAddContent)
+ {
+ rContent.clear();
+ }
+ }
+ }
+ else
+ {
+ // An empty clipping path will completely clip away the element that had
+ // the clip-path property applied. (Svg spec)
+ rContent.clear();
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgdocument.cxx b/svgio/source/svgreader/svgdocument.cxx
new file mode 100644
index 0000000000..69066c96f8
--- /dev/null
+++ b/svgio/source/svgreader/svgdocument.cxx
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgdocument.hxx>
+#include <utility>
+
+namespace svgio::svgreader
+{
+ SvgDocument::SvgDocument(OUString aAbsolutePath)
+ : maAbsolutePath(std::move(aAbsolutePath))
+ {
+ }
+
+ SvgDocument::~SvgDocument()
+ {
+ }
+
+ void SvgDocument::appendNode(std::unique_ptr<SvgNode> pNode)
+ {
+ assert(pNode);
+ maNodes.push_back(std::move(pNode));
+ }
+
+ void SvgDocument::addSvgNodeToMapper(const OUString& rStr, const SvgNode& rNode)
+ {
+ if(!rStr.isEmpty())
+ {
+ maIdTokenMapperList.emplace(rStr, &rNode);
+ }
+ }
+
+ void SvgDocument::removeSvgNodeFromMapper(const OUString& rStr)
+ {
+ if(!rStr.isEmpty())
+ {
+ maIdTokenMapperList.erase(rStr);
+ }
+ }
+
+ const SvgNode* SvgDocument::findSvgNodeById(const OUString& rStr) const
+ {
+ const IdTokenMapper::const_iterator aResult(maIdTokenMapperList.find(rStr));
+
+ if(aResult == maIdTokenMapperList.end())
+ {
+ return nullptr;
+ }
+ else
+ {
+ return aResult->second;
+ }
+ }
+
+ void SvgDocument::addSvgStyleAttributesToMapper(const OUString& rStr, const SvgStyleAttributes& rSvgStyleAttributes)
+ {
+ if(!rStr.isEmpty())
+ {
+ maIdStyleTokenMapperList.emplace(rStr, &rSvgStyleAttributes);
+ }
+ }
+
+ const SvgStyleAttributes* SvgDocument::findGlobalCssStyleAttributes(const OUString& rStr) const
+ {
+ const IdStyleTokenMapper::const_iterator aResult(maIdStyleTokenMapperList.find(rStr));
+
+ if(aResult == maIdStyleTokenMapperList.end())
+ {
+ return nullptr;
+ }
+ else
+ {
+ return aResult->second;
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx b/svgio/source/svgreader/svgdocumenthandler.cxx
new file mode 100644
index 0000000000..5e89edad6c
--- /dev/null
+++ b/svgio/source/svgreader/svgdocumenthandler.cxx
@@ -0,0 +1,603 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgdocumenthandler.hxx>
+#include <svgtoken.hxx>
+#include <svgsvgnode.hxx>
+#include <svggnode.hxx>
+#include <svganode.hxx>
+#include <svgnode.hxx>
+#include <svgpathnode.hxx>
+#include <svgrectnode.hxx>
+#include <svggradientnode.hxx>
+#include <svggradientstopnode.hxx>
+#include <svgsymbolnode.hxx>
+#include <svgusenode.hxx>
+#include <svgcirclenode.hxx>
+#include <svgellipsenode.hxx>
+#include <svglinenode.hxx>
+#include <svgpolynode.hxx>
+#include <svgtextnode.hxx>
+#include <svgcharacternode.hxx>
+#include <svgtspannode.hxx>
+#include <svgtrefnode.hxx>
+#include <svgtextpathnode.hxx>
+#include <svgstylenode.hxx>
+#include <svgimagenode.hxx>
+#include <svgclippathnode.hxx>
+#include <svgfecolormatrixnode.hxx>
+#include <svgfedropshadownode.hxx>
+#include <svgfefloodnode.hxx>
+#include <svgfeimagenode.hxx>
+#include <svgfegaussianblurnode.hxx>
+#include <svgfeoffsetnode.hxx>
+#include <svgfilternode.hxx>
+#include <svgmasknode.hxx>
+#include <svgmarkernode.hxx>
+#include <svgpatternnode.hxx>
+#include <svgtitledescnode.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/lang/Locale.hpp>
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
+
+using namespace com::sun::star;
+
+namespace svgio::svgreader
+{
+
+namespace
+{
+ svgio::svgreader::SvgCharacterNode* whiteSpaceHandling(svgio::svgreader::SvgNode const * pNode, svgio::svgreader::SvgTspanNode* pParentLine, svgio::svgreader::SvgCharacterNode* pLast)
+ {
+ if(pNode)
+ {
+ const auto& rChilds = pNode->getChildren();
+ const sal_uInt32 nCount(rChilds.size());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ svgio::svgreader::SvgNode* pCandidate = rChilds[a].get();
+
+ if(pCandidate)
+ {
+ switch(pCandidate->getType())
+ {
+ case SVGToken::Character:
+ {
+ // clean whitespace in text span
+ svgio::svgreader::SvgCharacterNode* pCharNode = static_cast< svgio::svgreader::SvgCharacterNode* >(pCandidate);
+
+ pCharNode->setParentLine(pParentLine);
+
+ pCharNode->whiteSpaceHandling();
+ pLast = pCharNode->addGap(pLast);
+
+ double fTextWidth(0.0);
+
+ const SvgStyleAttributes* pSvgStyleAttributes = pCharNode->getSvgStyleAttributes();
+
+ if(pSvgStyleAttributes)
+ {
+ const drawinglayer::attribute::FontAttribute aFontAttribute(
+ svgio::svgreader::SvgCharacterNode::getFontAttribute(*pSvgStyleAttributes));
+
+ double fFontWidth(pSvgStyleAttributes->getFontSizeNumber().solve(*pCharNode));
+ double fFontHeight(fFontWidth);
+
+ css::lang::Locale aLocale;
+
+ drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
+ aTextLayouterDevice.setFontAttribute(aFontAttribute, fFontWidth, fFontHeight, aLocale);
+ fTextWidth = aTextLayouterDevice.getTextWidth(pCharNode->getText(), 0.0, pCharNode->getText().getLength());
+ }
+
+ pParentLine->concatenateTextLineWidth(fTextWidth);
+ break;
+ }
+ case SVGToken::Tspan:
+ {
+ svgio::svgreader::SvgTspanNode* pTspanNode = static_cast< svgio::svgreader::SvgTspanNode* >(pCandidate);
+
+ // If x or y exist it means it's a new line of text
+ if(!pTspanNode->getX().empty() || !pTspanNode->getY().empty())
+ pParentLine = pTspanNode;
+
+ // recursively clean whitespaces in subhierarchy
+ pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast);
+ break;
+ }
+ case SVGToken::TextPath:
+ case SVGToken::Tref:
+ {
+ // recursively clean whitespaces in subhierarchy
+ pLast = whiteSpaceHandling(pCandidate, pParentLine, pLast);
+ break;
+ }
+ default:
+ {
+ OSL_ENSURE(false, "Unexpected token inside SVGTokenText (!)");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return pLast;
+ }
+
+} // end anonymous namespace
+
+ SvgDocHdl::SvgDocHdl(const OUString& aAbsolutePath)
+ : maDocument(aAbsolutePath),
+ mpTarget(nullptr)
+ {
+ }
+
+ SvgDocHdl::~SvgDocHdl()
+ {
+ if (mpTarget)
+ {
+ OSL_ENSURE(false, "SvgDocHdl destructed with active target (!)");
+
+ while (mpTarget->getParent())
+ mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
+
+ const SvgNodeVector& rOwnedTopLevels = maDocument.getSvgNodeVector();
+ if (std::none_of(rOwnedTopLevels.begin(), rOwnedTopLevels.end(),
+ [&](std::unique_ptr<SvgNode> const & p) { return p.get() == mpTarget; }))
+ delete mpTarget;
+ }
+ OSL_ENSURE(maCssContents.empty(), "SvgDocHdl destructed with active css style stack entry (!)");
+ }
+
+ void SvgDocHdl::startDocument( )
+ {
+ OSL_ENSURE(!mpTarget, "Already a target at document start (!)");
+ OSL_ENSURE(maCssContents.empty(), "SvgDocHdl startDocument with active css style stack entry (!)");
+ }
+
+ void SvgDocHdl::endDocument( )
+ {
+ OSL_ENSURE(!mpTarget, "Still a target at document end (!)");
+ OSL_ENSURE(maCssContents.empty(), "SvgDocHdl endDocument with active css style stack entry (!)");
+ }
+
+ void SvgDocHdl::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs )
+ {
+ if(aName.isEmpty())
+ return;
+
+ const SVGToken aSVGToken(StrToSVGToken(aName, false));
+
+ switch (aSVGToken)
+ {
+ /// structural elements
+ case SVGToken::Symbol:
+ {
+ /// new basic node for Symbol. Content gets scanned, but
+ /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
+ mpTarget = new SvgSymbolNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Switch: //TODO: Support switch element
+ case SVGToken::Defs:
+ case SVGToken::G:
+ {
+ /// new node for Defs/G
+ mpTarget = new SvgGNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Svg:
+ {
+ /// new node for Svg
+ mpTarget = new SvgSvgNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Use:
+ {
+ /// new node for Use
+ mpTarget = new SvgUseNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::A:
+ {
+ /// new node for A
+ mpTarget = new SvgANode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// shape elements
+ case SVGToken::Circle:
+ {
+ /// new node for Circle
+ mpTarget = new SvgCircleNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Ellipse:
+ {
+ /// new node for Ellipse
+ mpTarget = new SvgEllipseNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Line:
+ {
+ /// new node for Line
+ mpTarget = new SvgLineNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Path:
+ {
+ /// new node for Path
+ mpTarget = new SvgPathNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Polygon:
+ {
+ /// new node for Polygon
+ mpTarget = new SvgPolyNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Polyline:
+ {
+ /// new node for Polyline
+ mpTarget = new SvgPolyNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Rect:
+ {
+ /// new node for Rect
+ mpTarget = new SvgRectNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Image:
+ {
+ /// new node for Image
+ mpTarget = new SvgImageNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// title and description
+ case SVGToken::Title:
+ case SVGToken::Desc:
+ {
+ /// new node for Title and/or Desc
+ mpTarget = new SvgTitleDescNode(aSVGToken, maDocument, mpTarget);
+ break;
+ }
+
+ /// gradients
+ case SVGToken::LinearGradient:
+ case SVGToken::RadialGradient:
+ {
+ mpTarget = new SvgGradientNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// gradient stops
+ case SVGToken::Stop:
+ {
+ mpTarget = new SvgGradientStopNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// text
+ case SVGToken::Text:
+ {
+ mpTarget = new SvgTextNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Tspan:
+ {
+ mpTarget = new SvgTspanNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Tref:
+ {
+ mpTarget = new SvgTrefNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::TextPath:
+ {
+ mpTarget = new SvgTextPathNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// styles (as stylesheets)
+ case SVGToken::Style:
+ {
+ SvgStyleNode* pNew = new SvgStyleNode(maDocument, mpTarget);
+ mpTarget = pNew;
+
+ // #i125326# there are attributes, read them. This will set isTextCss to false if
+ // type attribute is different to "text/css"
+ mpTarget->parseAttributes(xAttribs);
+
+ if(pNew->isTextCss())
+ {
+ // if it is a Css style, allow reading text between the start and end tag (see
+ // SvgDocHdl::characters for details)
+ maCssContents.emplace_back();
+ }
+ break;
+ }
+
+ /// structural elements clip-path and mask. Content gets scanned, but
+ /// will not be decomposed (see SvgNode::decomposeSvgNode and bReferenced)
+ case SVGToken::ClipPathNode:
+ {
+ /// new node for ClipPath
+ mpTarget = new SvgClipPathNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Mask:
+ {
+ /// new node for Mask
+ mpTarget = new SvgMaskNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeColorMatrix:
+ {
+ /// new node for feColorMatrix
+ mpTarget = new SvgFeColorMatrixNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeDropShadow:
+ {
+ /// new node for feDropShadow
+ mpTarget = new SvgFeDropShadowNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeFlood:
+ {
+ /// new node for feFlood
+ mpTarget = new SvgFeFloodNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeImage:
+ {
+ /// new node for feImage
+ mpTarget = new SvgFeImageNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeGaussianBlur:
+ {
+ /// new node for feGaussianBlur
+ mpTarget = new SvgFeGaussianBlurNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::FeOffset:
+ {
+ /// new node for feOffset
+ mpTarget = new SvgFeOffsetNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+ case SVGToken::Filter:
+ {
+ /// new node for Filter
+ mpTarget = new SvgFilterNode(aSVGToken, maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// structural element marker
+ case SVGToken::Marker:
+ {
+ /// new node for marker
+ mpTarget = new SvgMarkerNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ /// structural element pattern
+ case SVGToken::Pattern:
+ {
+ /// new node for pattern
+ mpTarget = new SvgPatternNode(maDocument, mpTarget);
+ mpTarget->parseAttributes(xAttribs);
+ break;
+ }
+
+ default:
+ {
+ mpTarget = new SvgNode(SVGToken::Unknown, maDocument, mpTarget);
+ break;
+ }
+ }
+ }
+
+ void SvgDocHdl::endElement( const OUString& aName )
+ {
+ if(aName.isEmpty())
+ return;
+
+ if(!mpTarget)
+ return;
+
+ const SVGToken aSVGToken(StrToSVGToken(aName, false));
+ SvgNode* pTextNode(SVGToken::Text == aSVGToken ? mpTarget : nullptr);
+ SvgStyleNode* pCssStyle(SVGToken::Style == aSVGToken ? static_cast< SvgStyleNode* >(mpTarget) : nullptr);
+ SvgTitleDescNode* pSvgTitleDescNode(SVGToken::Title == aSVGToken || SVGToken::Desc == aSVGToken ? static_cast< SvgTitleDescNode* >(mpTarget) : nullptr);
+
+ if(!mpTarget->getParent())
+ {
+ // last element closing, save this tree
+ maDocument.appendNode(std::unique_ptr<SvgNode>(mpTarget));
+ }
+
+ mpTarget = const_cast< SvgNode* >(mpTarget->getParent());
+
+ if (pSvgTitleDescNode && mpTarget)
+ {
+ const OUString& aText(pSvgTitleDescNode->getText());
+
+ if(!aText.isEmpty())
+ {
+ mpTarget->parseAttribute(aSVGToken, aText);
+ }
+ }
+
+ if(pCssStyle && pCssStyle->isTextCss())
+ {
+ // css style parsing
+ if(!maCssContents.empty())
+ {
+ // need to interpret css styles and remember them as StyleSheets
+ // #125325# Caution! the Css content may contain block comments
+ // (see http://www.w3.org/wiki/CSS_basics#CSS_comments). These need
+ // to be removed first
+ const OUString aCommentFreeSource(removeBlockComments(*(maCssContents.end() - 1)));
+
+ if(aCommentFreeSource.getLength())
+ {
+ pCssStyle->addCssStyleSheet(aCommentFreeSource);
+ }
+
+ maCssContents.pop_back();
+ }
+ else
+ {
+ OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
+ }
+ }
+
+ if(pTextNode)
+ {
+ // cleanup read strings
+ whiteSpaceHandling(pTextNode, static_cast< SvgTspanNode*>(pTextNode), nullptr);
+ }
+ }
+
+ void SvgDocHdl::characters( const OUString& aChars )
+ {
+ const sal_uInt32 nLength(aChars.getLength());
+
+ if(!(mpTarget && nLength))
+ return;
+
+ switch(mpTarget->getType())
+ {
+ case SVGToken::Text:
+ case SVGToken::Tspan:
+ case SVGToken::TextPath:
+ {
+ const auto& rChilds = mpTarget->getChildren();
+
+ if(!rChilds.empty())
+ {
+ SvgNode* pChild = rChilds[rChilds.size() - 1].get();
+ if ( pChild->getType() == SVGToken::Character )
+ {
+ SvgCharacterNode& rSvgCharacterNode = static_cast< SvgCharacterNode& >(*pChild);
+
+ // concatenate to current character span
+ rSvgCharacterNode.concatenate(aChars);
+ break;
+ }
+ }
+
+ // add character span as simplified tspan (no arguments)
+ // as direct child of SvgTextNode/SvgTspanNode/SvgTextPathNode
+ new SvgCharacterNode(maDocument, mpTarget, aChars);
+ break;
+ }
+ case SVGToken::Style:
+ {
+ SvgStyleNode& rSvgStyleNode = static_cast< SvgStyleNode& >(*mpTarget);
+
+ if(rSvgStyleNode.isTextCss())
+ {
+ // collect characters for css style
+ if(!maCssContents.empty())
+ {
+ const OUString aTrimmedChars(aChars.trim());
+
+ if(!aTrimmedChars.isEmpty())
+ {
+ std::vector< OUString >::iterator aString(maCssContents.end() - 1);
+ (*aString) += aTrimmedChars;
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Closing CssStyle, but no collector string on stack (!)");
+ }
+ }
+ break;
+ }
+ case SVGToken::Title:
+ case SVGToken::Desc:
+ {
+ SvgTitleDescNode& rSvgTitleDescNode = static_cast< SvgTitleDescNode& >(*mpTarget);
+
+ // add text directly to SvgTitleDescNode
+ rSvgTitleDescNode.concatenate(aChars);
+ break;
+ }
+ default:
+ {
+ // characters not used by a known node
+ break;
+ }
+ }
+ }
+
+ void SvgDocHdl::ignorableWhitespace(const OUString& /*aWhitespaces*/)
+ {
+ }
+
+ void SvgDocHdl::processingInstruction(const OUString& /*aTarget*/, const OUString& /*aData*/)
+ {
+ }
+
+ void SvgDocHdl::setDocumentLocator(const uno::Reference< xml::sax::XLocator >& /*xLocator*/)
+ {
+ }
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgellipsenode.cxx b/svgio/source/svgreader/svgellipsenode.cxx
new file mode 100644
index 0000000000..1843a017c5
--- /dev/null
+++ b/svgio/source/svgreader/svgellipsenode.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgellipsenode.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+namespace svgio::svgreader
+{
+ SvgEllipseNode::SvgEllipseNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Ellipse, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maCx(0),
+ maCy(0),
+ maRx(0),
+ maRy(0)
+ {
+ }
+
+ SvgEllipseNode::~SvgEllipseNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgEllipseNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgEllipseNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Cx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCx = aNum;
+ }
+ break;
+ }
+ case SVGToken::Cy:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCy = aNum;
+ }
+ break;
+ }
+ case SVGToken::Rx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maRx = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Ry:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maRy = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgEllipseNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!(pStyle && getRx().isSet() && getRy().isSet()))
+ return;
+
+ const double fRx(getRx().solve(*this, NumberType::xcoordinate));
+ const double fRy(getRy().solve(*this, NumberType::ycoordinate));
+
+ if(fRx <= 0.0 || fRy <= 0.0)
+ return;
+
+ const basegfx::B2DPolygon aPath(
+ basegfx::utils::createPolygonFromEllipse(
+ basegfx::B2DPoint(
+ getCx().isSet() ? getCx().solve(*this, NumberType::xcoordinate) : 0.0,
+ getCy().isSet() ? getCy().solve(*this, NumberType::ycoordinate) : 0.0),
+ fRx, fRy));
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(basegfx::B2DPolyPolygon(aPath), aNewTarget, nullptr);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfecolormatrixnode.cxx b/svgio/source/svgreader/svgfecolormatrixnode.cxx
new file mode 100644
index 0000000000..3a7943ac4e
--- /dev/null
+++ b/svgio/source/svgreader/svgfecolormatrixnode.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgfecolormatrixnode.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeColorMatrixNode::SvgFeColorMatrixNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeColorMatrix, rDocument, pParent)
+ , maType(ColorType::Matrix)
+{
+}
+
+SvgFeColorMatrixNode::~SvgFeColorMatrixNode() {}
+
+void SvgFeColorMatrixNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::Type:
+ {
+ if (!aContent.isEmpty())
+ {
+ if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"luminanceToAlpha"))
+ {
+ maType = ColorType::LuminanceToAlpha;
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"saturate"))
+ {
+ maType = ColorType::Saturate;
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hueRotate"))
+ {
+ maType = ColorType::HueRotate;
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"matrix"))
+ {
+ maType = ColorType::Matrix;
+ }
+ }
+ break;
+ }
+ case SVGToken::Values:
+ {
+ maValuesContent = aContent;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeColorMatrixNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ if (maType == ColorType::LuminanceToAlpha)
+ {
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
+ std::move(rTarget),
+ std::make_shared<basegfx::BColorModifier_luminance_to_alpha>()));
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+ else if (maType == ColorType::Saturate)
+ {
+ SvgNumber aNum(1.0);
+ (void)readSingleNumber(maValuesContent, aNum);
+
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
+ std::move(rTarget),
+ std::make_shared<basegfx::BColorModifier_saturate>(aNum.getNumber())));
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+ else if (maType == ColorType::HueRotate)
+ {
+ SvgNumber aNum(0.0);
+ (void)readSingleNumber(maValuesContent, aNum);
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
+ std::move(rTarget), std::make_shared<basegfx::BColorModifier_hueRotate>(
+ basegfx::deg2rad(aNum.getNumber()))));
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+ else if (maType == ColorType::Matrix)
+ {
+ std::vector<double> aVector = readFilterMatrix(maValuesContent, *this);
+
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
+ std::move(rTarget), std::make_shared<basegfx::BColorModifier_matrix>(aVector)));
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfedropshadownode.cxx b/svgio/source/svgreader/svgfedropshadownode.cxx
new file mode 100644
index 0000000000..c968f51f5c
--- /dev/null
+++ b/svgio/source/svgreader/svgfedropshadownode.cxx
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <svgfedropshadownode.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeDropShadowNode::SvgFeDropShadowNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeDropShadow, rDocument, pParent)
+ , maDx(0.0)
+ , maDy(0.0)
+ , maStdDeviation(0.0)
+ , maFloodColor(SvgPaint())
+ , maFloodOpacity(1.0)
+{
+}
+
+SvgFeDropShadowNode::~SvgFeDropShadowNode() {}
+
+void SvgFeDropShadowNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Dx:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maDx = aNum;
+ }
+ break;
+ }
+ case SVGToken::Dy:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maDy = aNum;
+ }
+ break;
+ }
+ case SVGToken::StdDeviation:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maStdDeviation = aNum;
+ }
+ break;
+ }
+ case SVGToken::FloodColor:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if (readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ maFloodColor = aSvgPaint;
+ }
+ break;
+ }
+ case SVGToken::FloodOpacity:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maFloodOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(),
+ aNum.isSet());
+ }
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeDropShadowNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ basegfx::B2DHomMatrix aTransform;
+ if (maDx.isSet() || maDy.isSet())
+ {
+ aTransform.translate(maDx.solve(*this, NumberType::xcoordinate),
+ maDy.solve(*this, NumberType::ycoordinate));
+ }
+
+ drawinglayer::primitive2d::Primitive2DContainer aTempTarget;
+
+ // Create the shadow
+ aTempTarget.append(drawinglayer::primitive2d::Primitive2DReference(
+ new drawinglayer::primitive2d::ShadowPrimitive2D(
+ aTransform, maFloodColor.getBColor(), maStdDeviation.getNumber(),
+ drawinglayer::primitive2d::Primitive2DContainer(rTarget))));
+
+ const double fOpacity(maFloodOpacity.solve(*this));
+ if (basegfx::fTools::less(fOpacity, 1.0))
+ {
+ // Apply transparence to the shadow
+ aTempTarget.append(drawinglayer::primitive2d::Primitive2DReference(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(std::move(aTempTarget),
+ 1.0 - fOpacity)));
+ }
+
+ // Append the original target
+ aTempTarget.append(rTarget);
+
+ rTarget = aTempTarget;
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfefloodnode.cxx b/svgio/source/svgreader/svgfefloodnode.cxx
new file mode 100644
index 0000000000..89f12c4a4d
--- /dev/null
+++ b/svgio/source/svgreader/svgfefloodnode.cxx
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <svgfefloodnode.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeFloodNode::SvgFeFloodNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeFlood, rDocument, pParent)
+ , maX(0.0)
+ , maY(0.0)
+ , maWidth(0.0)
+ , maHeight(0.0)
+ , maFloodColor(SvgPaint())
+ , maFloodOpacity(1.0)
+{
+}
+
+SvgFeFloodNode::~SvgFeFloodNode() {}
+
+void SvgFeFloodNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ if (aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ if (aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::FloodColor:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if (readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ maFloodColor = aSvgPaint;
+ }
+ break;
+ }
+ case SVGToken::FloodOpacity:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ maFloodOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(),
+ aNum.isSet());
+ }
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeFloodNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ const double fWidth(maWidth.solve(*this, NumberType::xcoordinate));
+ const double fHeight(maHeight.solve(*this, NumberType::ycoordinate));
+
+ if (fWidth <= 0.0 || fHeight <= 0.0)
+ return;
+
+ const double fX(maX.solve(*this, NumberType::xcoordinate));
+ const double fY(maY.solve(*this, NumberType::ycoordinate));
+ const basegfx::B2DRange aRange(fX, fY, fX + fWidth, fY + fHeight);
+
+ basegfx::B2DPolyPolygon aPolygon(basegfx::utils::createPolygonFromRect(aRange));
+ drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(aPolygon,
+ maFloodColor.getBColor()));
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+
+ const double fOpacity(maFloodOpacity.solve(*this));
+
+ if (basegfx::fTools::less(fOpacity, 1.0))
+ {
+ xRef = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(std::move(rTarget),
+ 1.0 - fOpacity);
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfegaussianblurnode.cxx b/svgio/source/svgreader/svgfegaussianblurnode.cxx
new file mode 100644
index 0000000000..53b1513d25
--- /dev/null
+++ b/svgio/source/svgreader/svgfegaussianblurnode.cxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgfegaussianblurnode.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeGaussianBlurNode::SvgFeGaussianBlurNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeGaussianBlur, rDocument, pParent)
+ , maStdDeviation(SvgNumber(0.0))
+{
+}
+
+SvgFeGaussianBlurNode::~SvgFeGaussianBlurNode() {}
+
+void SvgFeGaussianBlurNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::StdDeviation:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ if (aNum.isPositive())
+ {
+ maStdDeviation = aNum;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeGaussianBlurNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::SoftEdgePrimitive2D(maStdDeviation.getNumber(),
+ std::move(rTarget)));
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfeimagenode.cxx b/svgio/source/svgreader/svgfeimagenode.cxx
new file mode 100644
index 0000000000..7174bcaf4a
--- /dev/null
+++ b/svgio/source/svgreader/svgfeimagenode.cxx
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <svgfeimagenode.hxx>
+#include <o3tl/string_view.hxx>
+#include <svgdocument.hxx>
+#include <comphelper/base64.hxx>
+#include <tools/stream.hxx>
+#include <rtl/uri.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeImageNode::SvgFeImageNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeImage, rDocument, pParent)
+{
+}
+
+SvgFeImageNode::~SvgFeImageNode() {}
+
+void SvgFeImageNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ const sal_Int32 nLen(aContent.getLength());
+
+ if (nLen)
+ {
+ OUString aXLink;
+ readImageLink(aContent, aXLink, maUrl, maData);
+ }
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeImageNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ BitmapEx aBitmapEx;
+
+ if (!maData.isEmpty())
+ {
+ // use embedded base64 encoded data
+ css::uno::Sequence<sal_Int8> aPass;
+ ::comphelper::Base64::decode(aPass, maData);
+
+ if (aPass.hasElements())
+ {
+ SvMemoryStream aStream(aPass.getArray(), aPass.getLength(), StreamMode::READ);
+ Graphic aGraphic;
+
+ if (ERRCODE_NONE
+ == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, u"", aStream))
+ {
+ aBitmapEx = aGraphic.GetBitmapEx();
+ }
+ }
+ }
+ else if (!maUrl.isEmpty())
+ {
+ const OUString& rPath = getDocument().getAbsolutePath();
+ OUString aAbsUrl;
+ try
+ {
+ aAbsUrl = rtl::Uri::convertRelToAbs(rPath, maUrl);
+ }
+ catch (rtl::MalformedUriException& e)
+ {
+ SAL_WARN("svg", "caught rtl::MalformedUriException \"" << e.getMessage() << "\"");
+ }
+
+ if (!aAbsUrl.isEmpty() && rPath != aAbsUrl)
+ {
+ SvFileStream aStream(aAbsUrl, StreamMode::STD_READ);
+ Graphic aGraphic;
+
+ if (ERRCODE_NONE
+ == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aAbsUrl, aStream))
+ {
+ aBitmapEx = aGraphic.GetBitmapEx();
+ }
+ }
+ }
+
+ if (!aBitmapEx.IsEmpty() && 0 != aBitmapEx.GetSizePixel().Width()
+ && 0 != aBitmapEx.GetSizePixel().Height())
+ {
+ basegfx::B2DRange aViewBox
+ = rTarget.getB2DRange(drawinglayer::geometry::ViewInformation2D());
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::BitmapPrimitive2D(
+ aBitmapEx, basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aViewBox.getRange(), aViewBox.getMinimum())));
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+ }
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfeoffsetnode.cxx b/svgio/source/svgreader/svgfeoffsetnode.cxx
new file mode 100644
index 0000000000..324920cde1
--- /dev/null
+++ b/svgio/source/svgreader/svgfeoffsetnode.cxx
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <svgfeoffsetnode.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+SvgFeOffsetNode::SvgFeOffsetNode(SvgDocument& rDocument, SvgNode* pParent)
+ : SvgFilterNode(SVGToken::FeOffset, rDocument, pParent)
+ , maDx(SvgNumber(0.0))
+ , maDy(SvgNumber(0.0))
+{
+}
+
+SvgFeOffsetNode::~SvgFeOffsetNode() {}
+
+void SvgFeOffsetNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+{
+ // parse own
+ switch (aSVGToken)
+ {
+ case SVGToken::Dx:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ if (aNum.isPositive())
+ {
+ maDx = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Dy:
+ {
+ SvgNumber aNum;
+
+ if (readSingleNumber(aContent, aNum))
+ {
+ if (aNum.isPositive())
+ {
+ maDy = aNum;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void SvgFeOffsetNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ basegfx::B2DHomMatrix aTransform;
+
+ if (maDx.isSet() || maDy.isSet())
+ {
+ aTransform.translate(maDx.solve(*this, NumberType::xcoordinate),
+ maDy.solve(*this, NumberType::ycoordinate));
+ }
+
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(aTransform, std::move(rTarget)));
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer{ xRef };
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgfilternode.cxx b/svgio/source/svgreader/svgfilternode.cxx
new file mode 100644
index 0000000000..3e21e9c2ad
--- /dev/null
+++ b/svgio/source/svgreader/svgfilternode.cxx
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgfilternode.hxx>
+#include <svgfecolormatrixnode.hxx>
+#include <svgfedropshadownode.hxx>
+#include <svgfefloodnode.hxx>
+#include <svgfeimagenode.hxx>
+#include <svgfegaussianblurnode.hxx>
+#include <svgfeoffsetnode.hxx>
+
+namespace svgio::svgreader
+{
+SvgFilterNode::SvgFilterNode(SVGToken aType, SvgDocument& rDocument, SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent)
+{
+}
+
+SvgFilterNode::~SvgFilterNode() {}
+
+void SvgFilterNode::apply(drawinglayer::primitive2d::Primitive2DContainer& rTarget) const
+{
+ if (rTarget.empty())
+ return;
+
+ const auto& rChildren = getChildren();
+ const sal_uInt32 nCount(rChildren.size());
+
+ // apply children's filters
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ SvgFilterNode* pFilterNode = dynamic_cast<SvgFilterNode*>(rChildren[a].get());
+ if (pFilterNode)
+ pFilterNode->apply(rTarget);
+ }
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svggnode.cxx b/svgio/source/svgreader/svggnode.cxx
new file mode 100644
index 0000000000..b70e780206
--- /dev/null
+++ b/svgio/source/svgreader/svggnode.cxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svggnode.hxx>
+#include <osl/diagnose.h>
+
+namespace svgio::svgreader
+{
+ SvgGNode::SvgGNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ OSL_ENSURE(aType == SVGToken::Defs || aType == SVGToken::G, "SvgGNode should only be used for Group and Defs (!)");
+ }
+
+ SvgGNode::~SvgGNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgGNode::getSvgStyleAttributes() const
+ {
+ // tdf#98599 attributes may be inherit by the children, therefore read them
+ // #i125258# for SVGToken::G take CssStyles into account
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgGNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgGNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ if(SVGToken::Defs == getType())
+ {
+ // #i125258# no decompose needed for defs element, call parent for SVGTokenDefs
+ SvgNode::decomposeSvgNode(rTarget, bReferenced);
+ }
+ else
+ {
+ // #i125258# for SVGTokenG decompose children
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(pStyle)
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aContent;
+
+ // decompose children
+ SvgNode::decomposeSvgNode(aContent, bReferenced);
+
+ if(!aContent.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aContent), getTransform());
+ }
+ }
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svggradientnode.cxx b/svgio/source/svgreader/svggradientnode.cxx
new file mode 100644
index 0000000000..723dac6d5f
--- /dev/null
+++ b/svgio/source/svgreader/svggradientnode.cxx
@@ -0,0 +1,501 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svggradientnode.hxx>
+#include <svgdocument.hxx>
+#include <svggradientstopnode.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+namespace svgio::svgreader
+{
+ void SvgGradientNode::tryToFindLink()
+ {
+ if(!mpXLink && !maXLink.isEmpty())
+ {
+ mpXLink = dynamic_cast< const SvgGradientNode* >(getDocument().findSvgNodeById(maXLink));
+ }
+ }
+
+ SvgGradientNode::SvgGradientNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maGradientUnits(SvgUnits::objectBoundingBox),
+ maSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Pad),
+ mbResolvingLink(false),
+ mpXLink(nullptr)
+ {
+ OSL_ENSURE(aType == SVGToken::LinearGradient || aType == SVGToken::RadialGradient, "SvgGradientNode should only be used for Linear and Radial gradient (!)");
+ }
+
+ SvgGradientNode::~SvgGradientNode()
+ {
+ // do NOT delete mpXLink, it's only referenced, not owned
+ }
+
+ const SvgStyleAttributes* SvgGradientNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgGradientNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X1:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX1 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y1:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY1 = aNum;
+ }
+ break;
+ }
+ case SVGToken::X2:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX2 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y2:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY2 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Cx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCx = aNum;
+ }
+ break;
+ }
+ case SVGToken::Cy:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maCy = aNum;
+ }
+ break;
+ }
+ case SVGToken::Fx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maFx = aNum;
+ }
+ break;
+ }
+ case SVGToken::Fy:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maFy = aNum;
+ }
+ break;
+ }
+ case SVGToken::R:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maR = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::GradientUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setGradientUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setGradientUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ case SVGToken::SpreadMethod:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"pad"))
+ {
+ setSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Pad);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"reflect"))
+ {
+ setSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Reflect);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"repeat"))
+ {
+ setSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Repeat);
+ }
+ }
+ break;
+ }
+ case SVGToken::GradientTransform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setGradientTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ readLocalLink(aContent, maXLink);
+ tryToFindLink();
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgGradientNode::collectGradientEntries(drawinglayer::primitive2d::SvgGradientEntryVector& aVector) const
+ {
+ if(getChildren().empty())
+ {
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ mpXLink->collectGradientEntries(aVector);
+ mbResolvingLink = false;
+ }
+ }
+ else
+ {
+ const sal_uInt32 nCount(getChildren().size());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const SvgGradientStopNode* pCandidate = dynamic_cast< const SvgGradientStopNode* >(getChildren()[a].get());
+
+ if(pCandidate)
+ {
+ const SvgStyleAttributes* pStyle = pCandidate->getSvgStyleAttributes();
+
+ if(pStyle)
+ {
+ const SvgNumber aOffset(pCandidate->getOffset());
+ double fOffset(0.0);
+
+ if(SvgUnit::percent == aOffset.getUnit())
+ {
+ // percent is not relative to distances in ColorStop context, solve locally
+ fOffset = aOffset.getNumber() * 0.01;
+ }
+ else
+ {
+ fOffset = aOffset.solve(*this);
+ }
+
+ if(fOffset < 0.0)
+ {
+ OSL_ENSURE(false, "OOps, SvgGradientStopNode with offset out of range (!)");
+ fOffset = 0.0;
+ }
+ else if(fOffset > 1.0)
+ {
+ OSL_ENSURE(false, "OOps, SvgGradientStopNode with offset out of range (!)");
+ fOffset = 1.0;
+ }
+
+ aVector.emplace_back(
+ fOffset,
+ pStyle->getStopColor(),
+ pStyle->getStopOpacity().solve(*this));
+ }
+ else
+ {
+ OSL_ENSURE(false, "OOps, SvgGradientStopNode without Style (!)");
+ }
+ }
+ }
+ }
+ }
+
+ SvgNumber SvgGradientNode::getX1() const
+ {
+ if(maX1.isSet())
+ {
+ return maX1;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getX1();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 0%
+ return SvgNumber(0.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getY1() const
+ {
+ if(maY1.isSet())
+ {
+ return maY1;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getY1();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 0%
+ return SvgNumber(0.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getX2() const
+ {
+ if(maX2.isSet())
+ {
+ return maX2;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getX2();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 100%
+ return SvgNumber(100.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getY2() const
+ {
+ if(maY2.isSet())
+ {
+ return maY2;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getY2();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 0%
+ return SvgNumber(0.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getCx() const
+ {
+ if(maCx.isSet())
+ {
+ return maCx;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getCx();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 50%
+ return SvgNumber(50.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getCy() const
+ {
+ if(maCy.isSet())
+ {
+ return maCy;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getCy();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 50%
+ return SvgNumber(50.0, SvgUnit::percent);
+ }
+
+ SvgNumber SvgGradientNode::getR() const
+ {
+ if(maR.isSet())
+ {
+ return maR;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getR();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ // default is 50%
+ return SvgNumber(50.0, SvgUnit::percent);
+ }
+
+ const SvgNumber* SvgGradientNode::getFx() const
+ {
+ if(maFx.isSet())
+ {
+ return &maFx;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getFx();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ const SvgNumber* SvgGradientNode::getFy() const
+ {
+ if(maFy.isSet())
+ {
+ return &maFy;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getFy();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ std::optional<basegfx::B2DHomMatrix> SvgGradientNode::getGradientTransform() const
+ {
+ if(mpaGradientTransform)
+ {
+ return mpaGradientTransform;
+ }
+
+ const_cast< SvgGradientNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getGradientTransform();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return std::nullopt;
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svggradientstopnode.cxx b/svgio/source/svgreader/svggradientstopnode.cxx
new file mode 100644
index 0000000000..d3b3001977
--- /dev/null
+++ b/svgio/source/svgreader/svggradientstopnode.cxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svggradientstopnode.hxx>
+
+namespace svgio::svgreader
+{
+ SvgGradientStopNode::SvgGradientStopNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Stop, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgGradientStopNode::~SvgGradientStopNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgGradientStopNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgGradientStopNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Offset:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maOffset = aNum;
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgimagenode.cxx b/svgio/source/svgreader/svgimagenode.cxx
new file mode 100644
index 0000000000..7e06813628
--- /dev/null
+++ b/svgio/source/svgreader/svgimagenode.cxx
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgimagenode.hxx>
+#include <svgdocument.hxx>
+#include <tools/stream.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <comphelper/base64.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+namespace svgio::svgreader
+{
+ SvgImageNode::SvgImageNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Rect, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maX(0),
+ maY(0),
+ maWidth(0),
+ maHeight(0)
+ {
+ }
+
+ SvgImageNode::~SvgImageNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgImageNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgImageNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::PreserveAspectRatio:
+ {
+ maSvgAspectRatio = readSvgAspectRatio(aContent);
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ const sal_Int32 nLen(aContent.getLength());
+
+ if(nLen)
+ {
+ readImageLink(aContent, maXLink, maUrl, maData);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ static void extractFromGraphic(
+ const Graphic& rGraphic,
+ drawinglayer::primitive2d::Primitive2DContainer& rEmbedded,
+ basegfx::B2DRange& rViewBox,
+ BitmapEx& rBitmapEx)
+ {
+ if(GraphicType::Bitmap == rGraphic.GetType())
+ {
+ if(rGraphic.getVectorGraphicData())
+ {
+ // embedded Svg
+ rEmbedded = rGraphic.getVectorGraphicData()->getPrimitive2DSequence();
+
+ // fill aViewBox
+ rViewBox = rGraphic.getVectorGraphicData()->getRange();
+ }
+ else
+ {
+ // get bitmap
+ rBitmapEx = rGraphic.GetBitmapEx();
+ }
+ }
+ else
+ {
+ // evtl. convert to bitmap
+ rBitmapEx = rGraphic.GetBitmapEx();
+ }
+ }
+
+ void SvgImageNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ // get size range and create path
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!(pStyle && getWidth().isSet() && getHeight().isSet()))
+ return;
+
+ const double fWidth(getWidth().solve(*this, NumberType::xcoordinate));
+ const double fHeight(getHeight().solve(*this, NumberType::ycoordinate));
+
+ if(fWidth <= 0.0 || fHeight <= 0.0)
+ return;
+
+ BitmapEx aBitmapEx;
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ // prepare Target and ViewBox for evtl. AspectRatio mappings
+ const double fX(getX().isSet() ? getX().solve(*this, NumberType::xcoordinate) : 0.0);
+ const double fY(getY().isSet() ? getY().solve(*this, NumberType::ycoordinate) : 0.0);
+ const basegfx::B2DRange aTarget(fX, fY, fX + fWidth, fY + fHeight);
+ basegfx::B2DRange aViewBox(aTarget);
+
+ if(!maData.isEmpty())
+ {
+ // use embedded base64 encoded data
+ css::uno::Sequence< sal_Int8 > aPass;
+ ::comphelper::Base64::decode(aPass, maData);
+
+ if(aPass.hasElements())
+ {
+ SvMemoryStream aStream(aPass.getArray(), aPass.getLength(), StreamMode::READ);
+ Graphic aGraphic;
+
+ if(ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(
+ aGraphic,
+ u"",
+ aStream))
+ {
+ extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
+ }
+ }
+ }
+ else if(!maUrl.isEmpty())
+ {
+ const OUString& rPath = getDocument().getAbsolutePath();
+ OUString aAbsUrl;
+ try {
+ aAbsUrl = rtl::Uri::convertRelToAbs(rPath, maUrl);
+ } catch (rtl::MalformedUriException & e) {
+ SAL_WARN(
+ "svg",
+ "caught rtl::MalformedUriException \""
+ << e.getMessage() << "\"");
+ }
+
+ if (!aAbsUrl.isEmpty() && rPath != aAbsUrl)
+ {
+ SvFileStream aStream(aAbsUrl, StreamMode::STD_READ);
+ Graphic aGraphic;
+
+ if(ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(
+ aGraphic,
+ aAbsUrl,
+ aStream))
+ {
+ extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
+ }
+ }
+ }
+ else if(!maXLink.isEmpty())
+ {
+ const SvgNode* pXLink = getDocument().findSvgNodeById(maXLink);
+
+ if(pXLink && Display::None != pXLink->getDisplay())
+ {
+ pXLink->decomposeSvgNode(aNewTarget, true);
+
+ if(!aNewTarget.empty())
+ {
+ aViewBox = aNewTarget.getB2DRange(drawinglayer::geometry::ViewInformation2D());
+ }
+ }
+ }
+
+ if(!aBitmapEx.IsEmpty() && 0 != aBitmapEx.GetSizePixel().Width() && 0 != aBitmapEx.GetSizePixel().Height())
+ {
+ // calculate centered unit size
+ const double fAspectRatio = static_cast<double>(aBitmapEx.GetSizePixel().Width()) / static_cast<double>(aBitmapEx.GetSizePixel().Height());
+
+ if(basegfx::fTools::equal(fAspectRatio, 0.0))
+ {
+ // use unit range
+ aViewBox = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
+ }
+ else if(basegfx::fTools::more(fAspectRatio, 0.0))
+ {
+ // width bigger height
+ const double fHalfHeight((1.0 / fAspectRatio) * 0.5);
+ aViewBox = basegfx::B2DRange(
+ 0.0,
+ 0.5 - fHalfHeight,
+ 1.0,
+ 0.5 + fHalfHeight);
+ }
+ else
+ {
+ // height bigger width
+ const double fHalfWidth(fAspectRatio * 0.5);
+ aViewBox = basegfx::B2DRange(
+ 0.5 - fHalfWidth,
+ 0.0,
+ 0.5 + fHalfWidth,
+ 1.0);
+ }
+
+ // create content from created bitmap, use calculated unit range size
+ // as transformation to map the picture data correctly
+ aNewTarget.resize(1);
+ aNewTarget[0] = new drawinglayer::primitive2d::BitmapPrimitive2D(
+ aBitmapEx,
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aViewBox.getRange(),
+ aViewBox.getMinimum()));
+ }
+
+ if(aNewTarget.empty())
+ return;
+
+ // create mapping
+ const SvgAspectRatio& rRatio = maSvgAspectRatio;
+
+ // even when ratio is not set, use the defaults
+ // let mapping be created from SvgAspectRatio
+ const basegfx::B2DHomMatrix aEmbeddingTransform(rRatio.createMapping(aTarget, aViewBox));
+
+ if(!aEmbeddingTransform.isIdentity())
+ {
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aEmbeddingTransform,
+ std::move(aNewTarget)));
+
+ aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+
+ if(!rRatio.isMeetOrSlice())
+ {
+ // need to embed in MaskPrimitive2D to ensure clipping
+ const drawinglayer::primitive2d::Primitive2DReference xMask(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(aTarget)),
+ std::move(aNewTarget)));
+
+ aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xMask };
+ }
+
+ // embed and add to rTarget, take local extra-transform into account
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svglinenode.cxx b/svgio/source/svgreader/svglinenode.cxx
new file mode 100644
index 0000000000..41090dcee8
--- /dev/null
+++ b/svgio/source/svgreader/svglinenode.cxx
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svglinenode.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+namespace svgio::svgreader
+{
+ SvgLineNode::SvgLineNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Line, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maX1(0),
+ maY1(0),
+ maX2(0),
+ maY2(0)
+ {
+ }
+
+ SvgLineNode::~SvgLineNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgLineNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgLineNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X1:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX1 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y1:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY1 = aNum;
+ }
+ break;
+ }
+ case SVGToken::X2:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX2 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y2:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY2 = aNum;
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgLineNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!pStyle)
+ return;
+
+ const basegfx::B2DPoint X(
+ getX1().isSet() ? getX1().solve(*this, NumberType::xcoordinate) : 0.0,
+ getY1().isSet() ? getY1().solve(*this, NumberType::ycoordinate) : 0.0);
+ const basegfx::B2DPoint Y(
+ getX2().isSet() ? getX2().solve(*this, NumberType::xcoordinate) : 0.0,
+ getY2().isSet() ? getY2().solve(*this, NumberType::ycoordinate) : 0.0);
+
+ // X and Y may be equal, do not drop them. Markers or linecaps 'round' and 'square'
+ // need to be drawn for zero-length lines too.
+
+ basegfx::B2DPolygon aPath;
+
+ aPath.append(X);
+ aPath.append(Y);
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(basegfx::B2DPolyPolygon(aPath), aNewTarget, nullptr);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgmarkernode.cxx b/svgio/source/svgreader/svgmarkernode.cxx
new file mode 100644
index 0000000000..083471b49c
--- /dev/null
+++ b/svgio/source/svgreader/svgmarkernode.cxx
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgmarkernode.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ SvgMarkerNode::SvgMarkerNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Marker, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maRefX(0),
+ maRefY(0),
+ maMarkerUnits(MarkerUnits::strokeWidth),
+ maMarkerWidth(3),
+ maMarkerHeight(3),
+ mfAngle(0.0),
+ maMarkerOrient(MarkerOrient::notset)
+ {
+ }
+
+ SvgMarkerNode::~SvgMarkerNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgMarkerNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgMarkerNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::ViewBox:
+ {
+ const basegfx::B2DRange aRange(readViewBox(aContent, *this));
+
+ if(!aRange.isEmpty())
+ {
+ setViewBox(&aRange);
+ }
+ break;
+ }
+ case SVGToken::PreserveAspectRatio:
+ {
+ maSvgAspectRatio = readSvgAspectRatio(aContent);
+ break;
+ }
+ case SVGToken::RefX:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maRefX = aNum;
+ }
+ break;
+ }
+ case SVGToken::RefY:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maRefY = aNum;
+ }
+ break;
+ }
+ case SVGToken::MarkerUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"strokeWidth"))
+ {
+ setMarkerUnits(MarkerUnits::strokeWidth);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setMarkerUnits(MarkerUnits::userSpaceOnUse);
+ }
+ }
+ break;
+ }
+ case SVGToken::MarkerWidth:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maMarkerWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::MarkerHeight:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maMarkerHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Orient:
+ {
+ const sal_Int32 nLen(aContent.getLength());
+
+ if(nLen)
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"auto"))
+ {
+ setMarkerOrient(MarkerOrient::auto_start);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"auto-start-reverse"))
+ {
+ setMarkerOrient(MarkerOrient::auto_start_reverse);
+ }
+ else
+ {
+ sal_Int32 nPos(0);
+ double fAngle(0.0);
+
+ if(readAngle(aContent, nPos, fAngle, nLen))
+ {
+ setAngle(fAngle);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ const drawinglayer::primitive2d::Primitive2DContainer& SvgMarkerNode::getMarkerPrimitives() const
+ {
+ if(aPrimitives.empty() && Display::None != getDisplay())
+ {
+ decomposeSvgNode(const_cast< SvgMarkerNode* >(this)->aPrimitives, true);
+ }
+
+ return aPrimitives;
+ }
+
+ basegfx::B2DRange SvgMarkerNode::getCurrentViewPort() const
+ {
+ if(getViewBox())
+ {
+ return *(getViewBox());
+ }
+ else
+ {
+ return SvgNode::getCurrentViewPort();
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgmasknode.cxx b/svgio/source/svgreader/svgmasknode.cxx
new file mode 100644
index 0000000000..57c95ee01a
--- /dev/null
+++ b/svgio/source/svgreader/svgmasknode.cxx
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgmasknode.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ SvgMaskNode::SvgMaskNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Mask, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maX(SvgNumber(-10.0, SvgUnit::percent, true)),
+ maY(SvgNumber(-10.0, SvgUnit::percent, true)),
+ maWidth(SvgNumber(120.0, SvgUnit::percent, true)),
+ maHeight(SvgNumber(120.0, SvgUnit::percent, true)),
+ maMaskUnits(SvgUnits::objectBoundingBox),
+ maMaskContentUnits(SvgUnits::userSpaceOnUse)
+ {
+ }
+
+ SvgMaskNode::~SvgMaskNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgMaskNode::getSvgStyleAttributes() const
+ {
+ return &maSvgStyleAttributes;
+ }
+
+ void SvgMaskNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::MaskUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setMaskUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setMaskUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ case SVGToken::MaskContentUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setMaskContentUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setMaskContentUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgMaskNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ // decompose children
+ SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
+
+ if(aNewTarget.empty())
+ return;
+
+ if(getTransform())
+ {
+ // create embedding group element with transformation
+ drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ *getTransform(),
+ std::move(aNewTarget)));
+
+ aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+
+ // append to current target
+ rTarget.append(aNewTarget);
+ }
+
+ void SvgMaskNode::apply(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const
+ {
+ if(rTarget.empty() || Display::None == getDisplay())
+ return;
+
+ drawinglayer::primitive2d::Primitive2DContainer aMaskTarget;
+
+ // get mask definition as primitives
+ decomposeSvgNode(aMaskTarget, true);
+
+ if(!aMaskTarget.empty())
+ {
+ // get range of content to be masked
+ const basegfx::B2DRange aContentRange(
+ rTarget.getB2DRange(
+ drawinglayer::geometry::ViewInformation2D()));
+ const double fContentWidth(aContentRange.getWidth());
+ const double fContentHeight(aContentRange.getHeight());
+
+ if(fContentWidth > 0.0 && fContentHeight > 0.0)
+ {
+ // create OffscreenBufferRange
+ basegfx::B2DRange aOffscreenBufferRange;
+
+ if (SvgUnits::objectBoundingBox == maMaskUnits)
+ {
+ // fractions or percentages of the bounding box of the element to which the mask is applied
+ const double fX(SvgUnit::percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
+ const double fY(SvgUnit::percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
+ const double fW(SvgUnit::percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
+ const double fH(SvgUnit::percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
+
+ aOffscreenBufferRange = basegfx::B2DRange(
+ aContentRange.getMinX() + (fX * fContentWidth),
+ aContentRange.getMinY() + (fY * fContentHeight),
+ aContentRange.getMinX() + ((fX + fW) * fContentWidth),
+ aContentRange.getMinY() + ((fY + fH) * fContentHeight));
+ }
+ else
+ {
+ const double fX(getX().isSet() ? getX().solve(*this, NumberType::xcoordinate) : 0.0);
+ const double fY(getY().isSet() ? getY().solve(*this, NumberType::ycoordinate) : 0.0);
+
+ aOffscreenBufferRange = basegfx::B2DRange(
+ fX,
+ fY,
+ fX + (getWidth().isSet() ? getWidth().solve(*this, NumberType::xcoordinate) : 0.0),
+ fY + (getHeight().isSet() ? getHeight().solve(*this, NumberType::ycoordinate) : 0.0));
+ }
+
+ if (SvgUnits::objectBoundingBox == maMaskContentUnits)
+ {
+ // mask is object-relative, embed in content transformation
+ drawinglayer::primitive2d::Primitive2DReference xTransform(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aContentRange.getRange(),
+ aContentRange.getMinimum()),
+ std::move(aMaskTarget)));
+
+ aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
+ }
+ else // userSpaceOnUse
+ {
+ // #i124852#
+ if(pTransform)
+ {
+ drawinglayer::primitive2d::Primitive2DReference xTransform(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ *pTransform,
+ std::move(aMaskTarget)));
+
+ aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
+ }
+ }
+
+ // embed content to a ModifiedColorPrimitive2D since the definitions
+ // how content is used as alpha is special for Svg
+ {
+ drawinglayer::primitive2d::Primitive2DReference xInverseMask(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
+ std::move(aMaskTarget),
+ std::make_shared<basegfx::BColorModifier_luminance_to_alpha>()));
+
+ aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer { xInverseMask };
+ }
+
+ // prepare new content
+ drawinglayer::primitive2d::Primitive2DReference xNewContent(
+ new drawinglayer::primitive2d::TransparencePrimitive2D(
+ std::move(rTarget),
+ std::move(aMaskTarget)));
+
+ // output up to now is defined by aContentRange and mask is oriented
+ // relative to it. It is possible that aOffscreenBufferRange defines
+ // a smaller area. In that case, embed to a mask primitive
+ if(!aOffscreenBufferRange.isInside(aContentRange))
+ {
+ xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ aOffscreenBufferRange)),
+ drawinglayer::primitive2d::Primitive2DContainer { xNewContent });
+ }
+
+ // redefine target. Use TransparencePrimitive2D with created mask
+ // geometry
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer { xNewContent };
+ }
+ else
+ {
+ // content is geometrically empty
+ rTarget.clear();
+ }
+ }
+ else
+ {
+ // An empty clipping path will completely clip away the element that had
+ // the clip-path property applied. (Svg spec)
+ rTarget.clear();
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgnode.cxx b/svgio/source/svgreader/svgnode.cxx
new file mode 100644
index 0000000000..0ae4e80363
--- /dev/null
+++ b/svgio/source/svgreader/svgnode.cxx
@@ -0,0 +1,778 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgdocument.hxx>
+#include <svgnode.hxx>
+#include <svgstyleattributes.hxx>
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <tools/urlobj.hxx>
+
+
+namespace svgio::svgreader
+{
+ /// #i125258#
+ bool SvgNode::supportsParentStyle() const
+ {
+ return true;
+ }
+
+ const SvgStyleAttributes* SvgNode::getSvgStyleAttributes() const
+ {
+ return nullptr;
+ }
+
+ void SvgNode::addCssStyle(const SvgDocument& rDocument, const OUString& aConcatenated)
+ {
+ const SvgStyleAttributes* pNew = rDocument.findGlobalCssStyleAttributes(aConcatenated);
+
+ if(pNew)
+ {
+ // add CssStyle if found
+ maCssStyleVector.push_back(pNew);
+ }
+ }
+
+namespace {
+ std::vector< OUString > parseClass(const SvgNode& rNode)
+ {
+ std::vector< OUString > aParts;
+
+ // check for 'class' references (a list of entries is allowed)
+ if(rNode.getClass())
+ {
+ const OUString& rClassList = *rNode.getClass();
+ const sal_Int32 nLen(rClassList.getLength());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ OUStringBuffer aToken;
+
+ while(nPos < nLen)
+ {
+ const sal_Int32 nInitPos(nPos);
+ copyToLimiter(rClassList, u' ', nPos, aToken, nLen);
+ skip_char(rClassList, u' ', nPos, nLen);
+ const OUString aPart(o3tl::trim(aToken));
+ aToken.setLength(0);
+
+ if(aPart.getLength())
+ {
+ aParts.push_back(aPart);
+ }
+
+ if(nInitPos == nPos)
+ {
+ OSL_ENSURE(false, "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+ }
+ }
+
+ return aParts;
+ }
+} //namespace
+
+ void SvgNode::fillCssStyleVectorUsingHierarchyAndSelectors(
+ const SvgNode& rCurrent,
+ std::u16string_view aConcatenated)
+ {
+ const SvgDocument& rDocument = getDocument();
+
+ if(!rDocument.hasGlobalCssStyleAttributes())
+ return;
+
+ const SvgNode* pParent = rCurrent.getParent();
+ OUString sCurrentType(SVGTokenToStr(rCurrent.getType()));
+
+ // check for ID (highest priority)
+ if(rCurrent.getId())
+ {
+ const OUString& rId = *rCurrent.getId();
+
+ if(rId.getLength())
+ {
+ const OUString aNewConcatenated("#" + rId + aConcatenated);
+ addCssStyle(rDocument, aNewConcatenated);
+
+ if(!sCurrentType.isEmpty())
+ addCssStyle(rDocument, sCurrentType + aNewConcatenated);
+
+ if(pParent)
+ {
+ // check for combined selectors at parent first so that higher specificity will be in front
+ fillCssStyleVectorUsingHierarchyAndSelectors(*pParent, aNewConcatenated);
+ }
+ }
+ }
+
+ std::vector <OUString> aClasses = parseClass(rCurrent);
+ for(const auto &aClass : aClasses)
+ {
+ const OUString aNewConcatenated("." + aClass + aConcatenated);
+ addCssStyle(rDocument, aNewConcatenated);
+
+ if(!sCurrentType.isEmpty())
+ addCssStyle(rDocument, sCurrentType + aNewConcatenated);
+
+ if(pParent)
+ {
+ // check for combined selectors at parent first so that higher specificity will be in front
+ fillCssStyleVectorUsingHierarchyAndSelectors(*pParent, aNewConcatenated);
+ }
+ }
+
+ if(!sCurrentType.isEmpty())
+ {
+ const OUString aNewConcatenated(sCurrentType + aConcatenated);
+ addCssStyle(rDocument, aNewConcatenated);
+ }
+
+ OUString sType(SVGTokenToStr(getType()));
+
+ // check for class-dependent references to CssStyles
+ if(sType.isEmpty())
+ return;
+
+ if(pParent)
+ {
+ // check for combined selectors at parent first so that higher specificity will be in front
+ fillCssStyleVectorUsingHierarchyAndSelectors(*pParent, sType);
+ }
+ }
+
+ void SvgNode::fillCssStyleVectorUsingParent(const SvgNode& rCurrent)
+ {
+ const SvgDocument& rDocument = getDocument();
+
+ if(!rDocument.hasGlobalCssStyleAttributes())
+ return;
+
+ const SvgNode* pParent = rCurrent.getParent();
+
+ if (!pParent)
+ return;
+
+ OUString sParentId;
+ if (pParent->getId().has_value())
+ {
+ sParentId = pParent->getId().value();
+ }
+ std::vector <OUString> aParentClasses = parseClass(*pParent);
+ OUString sParentType(SVGTokenToStr(pParent->getType()));
+
+ if(rCurrent.getId())
+ {
+ const OUString& rId = *rCurrent.getId();
+
+ if(!rId.isEmpty())
+ {
+ if (!sParentId.isEmpty())
+ {
+ const OUString aConcatenated("#" + sParentId + ">#" + rId);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ for(const auto &aParentClass : aParentClasses)
+ {
+ const OUString aConcatenated("." + aParentClass + ">#" + rId);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ if (!sParentType.isEmpty())
+ {
+ const OUString aConcatenated(sParentType + ">#" + rId);
+ addCssStyle(rDocument, aConcatenated);
+ }
+ }
+
+ }
+
+ std::vector <OUString> aClasses = parseClass(rCurrent);
+ for(const auto &aClass : aClasses)
+ {
+
+ if (!sParentId.isEmpty())
+ {
+ const OUString aConcatenated("#" + sParentId + ">." + aClass);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ for(const auto &aParentClass : aParentClasses)
+ {
+ const OUString aConcatenated("." + aParentClass + ">." + aClass);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ if (!sParentType.isEmpty())
+ {
+ const OUString aConcatenated(sParentType + ">." + aClass);
+ addCssStyle(rDocument, aConcatenated);
+ }
+ }
+
+ OUString sCurrentType(SVGTokenToStr(getType()));
+
+ if(!sCurrentType.isEmpty())
+ {
+ if (!sParentId.isEmpty())
+ {
+ const OUString aConcatenated("#" + sParentId + ">" + sCurrentType);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ for(const auto &aParentClass : aParentClasses)
+ {
+ const OUString aConcatenated("." + aParentClass + ">" + sCurrentType);
+ addCssStyle(rDocument, aConcatenated);
+ }
+
+ if (!sParentType.isEmpty())
+ {
+ const OUString aConcatenated(sParentType + ">" + sCurrentType);
+ addCssStyle(rDocument, aConcatenated);
+ }
+ }
+ }
+
+ void SvgNode::fillCssStyleVector(const SvgStyleAttributes& rOriginal)
+ {
+ OSL_ENSURE(!mbCssStyleVectorBuilt, "OOps, fillCssStyleVector called double ?!?");
+ mbCssStyleVectorBuilt = true;
+
+ // #i125293# If we have CssStyles we need to build a linked list of SvgStyleAttributes
+ // which represent this for the current object. There are various methods to
+ // specify CssStyles which need to be taken into account in a given order:
+ // - local CssStyle (independent from global CssStyles at SvgDocument)
+ // - 'id' CssStyle
+ // - 'class' CssStyle(s)
+ // - type-dependent elements (e..g. 'rect' for all rect elements)
+ // - Css selector '*'
+ // - local attributes (rOriginal)
+ // - inherited attributes (up the hierarchy)
+ // The first four will be collected in maCssStyleVector for the current element
+ // (once, this will not change) and be linked in the needed order using the
+ // get/setCssStyleParent at the SvgStyleAttributes which will be used preferred in
+ // member evaluation over the existing parent hierarchy
+
+ // check for local CssStyle with highest priority
+ if(mpLocalCssStyle)
+ {
+ // if we have one, use as first entry
+ maCssStyleVector.push_back(mpLocalCssStyle.get());
+ }
+
+ // tdf#156038 check for child combinator
+ fillCssStyleVectorUsingParent(*this);
+
+ // check the hierarchy for concatenated patterns of Selectors
+ fillCssStyleVectorUsingHierarchyAndSelectors(*this, std::u16string_view());
+
+
+ // tdf#99115, Add css selector '*' style only if the element is on top of the hierarchy
+ // meaning its parent is <svg>
+ const SvgNode* pParent = this->getParent();
+
+ if(pParent && pParent->getType() == SVGToken::Svg)
+ {
+ // #i125329# find Css selector '*', add as last element if found
+ const SvgStyleAttributes* pNew = getDocument().findGlobalCssStyleAttributes("*");
+
+ if(pNew)
+ {
+ // add CssStyle for selector '*' if found
+ maCssStyleVector.push_back(pNew);
+ }
+ }
+
+ //local attributes
+ maCssStyleVector.push_back(&rOriginal);
+ }
+
+ const SvgStyleAttributes* SvgNode::checkForCssStyle(const SvgStyleAttributes& rOriginal) const
+ {
+ if(!mbCssStyleVectorBuilt)
+ {
+ // build needed CssStyleVector for local node
+ const_cast< SvgNode* >(this)->fillCssStyleVector(rOriginal);
+ }
+
+ if(maCssStyleVector.empty())
+ {
+ // return given original if no CssStyles found
+ return &rOriginal;
+ }
+ else
+ {
+ // #i125293# rOriginal will be the last element in the linked list; use no CssStyleParent
+ // there (reset it) to ensure that the parent hierarchy will be used when it's base
+ // is referenced. This new chaining inserts the CssStyles before the original style,
+ // this makes the whole process much safer since the original style when used will
+ // be not different to the situation without CssStyles; thus loops which may be caused
+ // by trying to use the parent hierarchy of the owner of the style will be avoided
+ // already in this mechanism. It's still good to keep the supportsParentStyle
+ // from #i125258# in place, though.
+ // This chain building using pointers will be done every time when checkForCssStyle
+ // is used (not the search, only the chaining). This is needed since the CssStyles
+ // themselves will be potentially used multiple times. It is not expensive since it's
+ // only changing some pointers.
+ // The alternative would be to create the style hierarchy for every element (or even
+ // for the element containing the hierarchy) in a vector of pointers and to use that.
+ // Resetting the CssStyleParent on rOriginal is probably not needed
+ // but simply safer to do.
+
+ // loop over the existing CssStyles and link them. There is a first one, take
+ // as current
+ SvgStyleAttributes* pCurrent = const_cast< SvgStyleAttributes* >(maCssStyleVector[0]);
+
+ for(size_t a(1); a < maCssStyleVector.size(); a++)
+ {
+ SvgStyleAttributes* pNext = const_cast< SvgStyleAttributes* >(maCssStyleVector[a]);
+
+ pCurrent->setCssStyleParent(pNext);
+ pCurrent = pNext;
+ }
+
+ // return 1st CssStyle as style chain start element (only for the
+ // local element, still no hierarchy used here)
+ return maCssStyleVector[0];
+ }
+ }
+
+ SvgNode::SvgNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : maType(aType),
+ mrDocument(rDocument),
+ mpParent(pParent),
+ mpAlternativeParent(nullptr),
+ maXmlSpace(XmlSpace::NotSet),
+ maDisplay(Display::Inline),
+ mbDecomposing(false),
+ mbCssStyleVectorBuilt(false)
+ {
+ if (pParent)
+ {
+ // tdf#150124 ignore when parent is unknown
+ if (pParent->getType() != SVGToken::Unknown)
+ pParent->maChildren.emplace_back(this);
+ else
+ mrDocument.addOrphanNode(this);
+ }
+ }
+
+ SvgNode::~SvgNode()
+ {
+ }
+
+ void SvgNode::readLocalCssStyle(std::u16string_view aContent)
+ {
+ if(!mpLocalCssStyle)
+ {
+ // create LocalCssStyle if needed but not yet added
+ mpLocalCssStyle.reset(new SvgStyleAttributes(*this));
+ }
+ else
+ {
+ // 2nd fill would be an error
+ OSL_ENSURE(false, "Svg node has two local CssStyles, this may lead to problems (!)");
+ }
+
+ if(mpLocalCssStyle)
+ {
+ // parse and set values to it
+ mpLocalCssStyle->readCssStyle(aContent);
+ }
+ else
+ {
+ OSL_ENSURE(false, "Could not get/create a local CssStyle for a node (!)");
+ }
+ }
+
+ void SvgNode::parseAttributes(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs)
+ {
+ // no longer need to pre-sort moving 'style' entries to the back so that
+ // values get overwritten - that was the previous, not complete solution for
+ // handling the priorities between svg and Css properties
+ const sal_uInt32 nAttributes(xAttribs->getLength());
+
+ for(sal_uInt32 a(0); a < nAttributes; a++)
+ {
+ const OUString aTokenName(xAttribs->getNameByIndex(a));
+ const SVGToken aSVGToken(StrToSVGToken(aTokenName, false));
+
+ parseAttribute(aSVGToken, xAttribs->getValueByIndex(a));
+ }
+ }
+
+ Display getDisplayFromContent(std::u16string_view aContent)
+ {
+ if(!aContent.empty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inline"))
+ {
+ return Display::Inline;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
+ {
+ return Display::None;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inherit"))
+ {
+ return Display::Inherit;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"block"))
+ {
+ return Display::Block;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"list-item"))
+ {
+ return Display::ListItem;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"run-in"))
+ {
+ return Display::RunIn;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"compact"))
+ {
+ return Display::Compact;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"marker"))
+ {
+ return Display::Marker;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table"))
+ {
+ return Display::Table;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inline-table"))
+ {
+ return Display::InlineTable;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-row-group"))
+ {
+ return Display::TableRowGroup;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-header-group"))
+ {
+ return Display::TableHeaderGroup;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-footer-group"))
+ {
+ return Display::TableFooterGroup;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-row"))
+ {
+ return Display::TableRow;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-column-group"))
+ {
+ return Display::TableColumnGroup;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-column"))
+ {
+ return Display::TableColumn;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-cell"))
+ {
+ return Display::TableCell;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"table-caption"))
+ {
+ return Display::TableCaption;
+ }
+ }
+
+ // return the default
+ return Display::Inline;
+ }
+
+ void SvgNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ switch(aSVGToken)
+ {
+ case SVGToken::Id:
+ {
+ if(!aContent.isEmpty())
+ {
+ setId(aContent);
+ }
+ break;
+ }
+ case SVGToken::Class:
+ {
+ if(!aContent.isEmpty())
+ {
+ setClass(aContent);
+ }
+ break;
+ }
+ case SVGToken::XmlSpace:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"default"))
+ {
+ setXmlSpace(XmlSpace::Default);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"preserve"))
+ {
+ setXmlSpace(XmlSpace::Preserve);
+ }
+ }
+ break;
+ }
+ case SVGToken::Display:
+ {
+ if(!aContent.isEmpty())
+ {
+ setDisplay(getDisplayFromContent(aContent));
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ if (mbDecomposing) //guard against infinite recurse
+ return;
+
+ if(Display::None == getDisplay())
+ {
+ return;
+ }
+
+ if(!bReferenced)
+ {
+ if(SVGToken::Defs == getType() ||
+ SVGToken::Symbol == getType() ||
+ SVGToken::ClipPathNode == getType() ||
+ SVGToken::Mask == getType() ||
+ SVGToken::Marker == getType() ||
+ SVGToken::Pattern == getType())
+ {
+ // do not decompose defs or symbol nodes (these hold only style-like
+ // objects which may be used by referencing them) except when doing
+ // so controlled referenced
+
+ // also do not decompose ClipPaths and Masks. These should be embedded
+ // in a defs node (which gets not decomposed by itself), but you never
+ // know
+
+ // also not directly used are Markers and Patterns, only indirectly used
+ // by reference
+
+ // #i121656# also do not decompose nodes which have display="none" set
+ // as property
+ return;
+ }
+ }
+
+ const auto& rChildren = getChildren();
+
+ if(rChildren.empty())
+ return;
+
+ mbDecomposing = true;
+
+ const sal_uInt32 nCount(rChildren.size());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ SvgNode* pCandidate = rChildren[a].get();
+
+ if(pCandidate && Display::None != pCandidate->getDisplay())
+ {
+ const auto& rGrandChildren = pCandidate->getChildren();
+ const SvgStyleAttributes* pChildStyles = pCandidate->getSvgStyleAttributes();
+ // decompose:
+ // - visible terminal nodes
+ // - all non-terminal nodes (might contain visible nodes down the hierarchy)
+ if( !rGrandChildren.empty() || ( pChildStyles && (Visibility::visible == pChildStyles->getVisibility())) )
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+ pCandidate->decomposeSvgNode(aNewTarget, bReferenced);
+
+ if(!aNewTarget.empty())
+ {
+ rTarget.append(aNewTarget);
+ }
+ }
+ }
+ else if(!pCandidate)
+ {
+ OSL_ENSURE(false, "Null-Pointer in child node list (!)");
+ }
+ }
+
+ if(!rTarget.empty())
+ {
+ const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
+ if(pStyles)
+ {
+ // check if we have Title or Desc
+ const OUString& rTitle = pStyles->getTitle();
+ const OUString& rDesc = pStyles->getDesc();
+
+ if(!rTitle.isEmpty() || !rDesc.isEmpty())
+ {
+ // default object name is empty
+ OUString aObjectName;
+
+ // use path as object name when outmost element
+ if (SVGToken::Svg == getType())
+ {
+ aObjectName = getDocument().getAbsolutePath();
+
+ if(!aObjectName.isEmpty())
+ {
+ INetURLObject aURL(aObjectName);
+
+ aObjectName = aURL.getName(
+ INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset);
+ }
+ }
+
+ // pack in ObjectInfoPrimitive2D group
+ drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
+ std::move(rTarget),
+ aObjectName,
+ rTitle,
+ rDesc));
+
+ rTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+ }
+ }
+ mbDecomposing = false;
+ }
+
+ basegfx::B2DRange SvgNode::getCurrentViewPort() const
+ {
+ if(getParent())
+ {
+ return getParent()->getCurrentViewPort();
+ }
+ else
+ {
+ return basegfx::B2DRange(); // return empty B2DRange
+ }
+ }
+
+ double SvgNode::getCurrentFontSizeInherited() const
+ {
+ if(getParent())
+ {
+ return getParent()->getCurrentFontSize();
+ }
+ else
+ {
+ return 0.0;
+ }
+ }
+
+ double SvgNode::getCurrentFontSize() const
+ {
+ if(getSvgStyleAttributes())
+ return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::xcoordinate);
+
+ return getCurrentFontSizeInherited();
+ }
+
+ double SvgNode::getCurrentXHeightInherited() const
+ {
+ if(getParent())
+ {
+ return getParent()->getCurrentXHeight();
+ }
+ else
+ {
+ return 0.0;
+ }
+ }
+
+ double SvgNode::getCurrentXHeight() const
+ {
+ if(getSvgStyleAttributes())
+ // for XHeight, use FontSize currently
+ return getSvgStyleAttributes()->getFontSizeNumber().solve(*this, NumberType::ycoordinate);
+
+ return getCurrentXHeightInherited();
+ }
+
+ void SvgNode::setId(OUString const & rId)
+ {
+ if(mpId)
+ {
+ mrDocument.removeSvgNodeFromMapper(*mpId);
+ mpId.reset();
+ }
+
+ mpId = rId;
+ mrDocument.addSvgNodeToMapper(*mpId, *this);
+ }
+
+ void SvgNode::setClass(OUString const & rClass)
+ {
+ if(mpClass)
+ {
+ mrDocument.removeSvgNodeFromMapper(*mpClass);
+ mpClass.reset();
+ }
+
+ mpClass = rClass;
+ mrDocument.addSvgNodeToMapper(*mpClass, *this);
+ }
+
+ XmlSpace SvgNode::getXmlSpace() const
+ {
+ if(maXmlSpace != XmlSpace::NotSet)
+ {
+ return maXmlSpace;
+ }
+
+ if(getParent())
+ {
+ return getParent()->getXmlSpace();
+ }
+
+ // default is XmlSpace::Default
+ return XmlSpace::Default;
+ }
+
+ void SvgNode::accept(Visitor & rVisitor)
+ {
+ rVisitor.visit(*this);
+ }
+} // end of namespace svgio
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgpaint.cxx b/svgio/source/svgreader/svgpaint.cxx
new file mode 100644
index 0000000000..725dd9174a
--- /dev/null
+++ b/svgio/source/svgreader/svgpaint.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgpaint.hxx>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgpathnode.cxx b/svgio/source/svgreader/svgpathnode.cxx
new file mode 100644
index 0000000000..307d5c9f03
--- /dev/null
+++ b/svgio/source/svgreader/svgpathnode.cxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgpathnode.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+namespace svgio::svgreader
+{
+ SvgPathNode::SvgPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Path, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgPathNode::~SvgPathNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgPathNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgPathNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::D:
+ {
+ basegfx::B2DPolyPolygon aPath;
+
+ if(basegfx::utils::importFromSvgD(aPath, aContent, false, &maHelpPointIndices))
+ {
+ if(aPath.count())
+ {
+ setPath(aPath);
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::PathLength:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maPathLength = aNum;
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgPathNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ // fill and/or stroke needed, also a path
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(pStyle && getPath())
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(*getPath(), aNewTarget, &maHelpPointIndices);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgpatternnode.cxx b/svgio/source/svgreader/svgpatternnode.cxx
new file mode 100644
index 0000000000..4c180558d5
--- /dev/null
+++ b/svgio/source/svgreader/svgpatternnode.cxx
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgpatternnode.hxx>
+#include <svgdocument.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ void SvgPatternNode::tryToFindLink()
+ {
+ if(!mpXLink && !maXLink.isEmpty())
+ {
+ mpXLink = dynamic_cast< const SvgPatternNode* >(getDocument().findSvgNodeById(maXLink));
+ }
+ }
+
+ SvgPatternNode::SvgPatternNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Pattern, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ mbResolvingLink(false),
+ mpXLink(nullptr)
+ {
+ }
+
+ SvgPatternNode::~SvgPatternNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgPatternNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgPatternNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::ViewBox:
+ {
+ const basegfx::B2DRange aRange(readViewBox(aContent, *this));
+
+ if(!aRange.isEmpty())
+ {
+ setViewBox(&aRange);
+ }
+ break;
+ }
+ case SVGToken::PreserveAspectRatio:
+ {
+ maSvgAspectRatio = readSvgAspectRatio(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::PatternUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setPatternUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setPatternUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ case SVGToken::PatternContentUnits:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
+ {
+ setPatternContentUnits(SvgUnits::userSpaceOnUse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
+ {
+ setPatternContentUnits(SvgUnits::objectBoundingBox);
+ }
+ }
+ break;
+ }
+ case SVGToken::PatternTransform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setPatternTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ readLocalLink(aContent, maXLink);
+ tryToFindLink();
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgPatternNode::getValuesRelative(double& rfX, double& rfY, double& rfW, double& rfH, const basegfx::B2DRange& rGeoRange, SvgNode const & rUser) const
+ {
+ double fTargetWidth(rGeoRange.getWidth());
+ double fTargetHeight(rGeoRange.getHeight());
+
+ if(fTargetWidth <= 0.0 || fTargetHeight <= 0.0)
+ return;
+
+ const SvgUnits aPatternUnits(getPatternUnits() ? *getPatternUnits() : SvgUnits::objectBoundingBox);
+
+ if (SvgUnits::objectBoundingBox == aPatternUnits)
+ {
+ rfW = (getWidth().isSet()) ? getWidth().getNumber() : 0.0;
+ rfH = (getHeight().isSet()) ? getHeight().getNumber() : 0.0;
+
+ if(SvgUnit::percent == getWidth().getUnit())
+ {
+ rfW *= 0.01;
+ }
+
+ if(SvgUnit::percent == getHeight().getUnit())
+ {
+ rfH *= 0.01;
+ }
+ }
+ else
+ {
+ rfW = (getWidth().isSet()) ? getWidth().solve(rUser, NumberType::xcoordinate) : 0.0;
+ rfH = (getHeight().isSet()) ? getHeight().solve(rUser, NumberType::ycoordinate) : 0.0;
+
+ // make relative to rGeoRange
+ rfW /= fTargetWidth;
+ rfH /= fTargetHeight;
+ }
+
+ if(rfW <= 0.0 || rfH <= 0.0)
+ return;
+
+ if (SvgUnits::objectBoundingBox == aPatternUnits)
+ {
+ rfX = (getX().isSet()) ? getX().getNumber() : 0.0;
+ rfY = (getY().isSet()) ? getY().getNumber() : 0.0;
+
+ if(SvgUnit::percent == getX().getUnit())
+ {
+ rfX *= 0.01;
+ }
+
+ if(SvgUnit::percent == getY().getUnit())
+ {
+ rfY *= 0.01;
+ }
+ }
+ else
+ {
+ rfX = (getX().isSet()) ? getX().solve(rUser, NumberType::xcoordinate) : 0.0;
+ rfY = (getY().isSet()) ? getY().solve(rUser, NumberType::ycoordinate) : 0.0;
+
+ // make relative to rGeoRange
+ rfX = (rfX - rGeoRange.getMinX()) / fTargetWidth;
+ rfY = (rfY - rGeoRange.getMinY()) / fTargetHeight;
+ }
+ }
+
+ const drawinglayer::primitive2d::Primitive2DContainer& SvgPatternNode::getPatternPrimitives() const
+ {
+ if(aPrimitives.empty() && Display::None != getDisplay())
+ {
+ decomposeSvgNode(const_cast< SvgPatternNode* >(this)->aPrimitives, true);
+ }
+
+ if(aPrimitives.empty() && !maXLink.isEmpty())
+ {
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const drawinglayer::primitive2d::Primitive2DContainer& ret = mpXLink->getPatternPrimitives();
+ mbResolvingLink = false;
+ return ret;
+ }
+ }
+
+ return aPrimitives;
+ }
+
+ basegfx::B2DRange SvgPatternNode::getCurrentViewPort() const
+ {
+ if(getViewBox())
+ {
+ return *(getViewBox());
+ }
+ else
+ {
+ return SvgNode::getCurrentViewPort();
+ }
+ }
+
+ const basegfx::B2DRange* SvgPatternNode::getViewBox() const
+ {
+ if(mpViewBox)
+ {
+ return mpViewBox.get();
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getViewBox();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ const SvgAspectRatio& SvgPatternNode::getSvgAspectRatio() const
+ {
+ if(maSvgAspectRatio.isSet())
+ {
+ return maSvgAspectRatio;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const SvgAspectRatio& ret = mpXLink->getSvgAspectRatio();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return maSvgAspectRatio;
+ }
+
+ const SvgNumber& SvgPatternNode::getX() const
+ {
+ if(maX.isSet())
+ {
+ return maX;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const SvgNumber& ret = mpXLink->getX();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return maX;
+ }
+
+ const SvgNumber& SvgPatternNode::getY() const
+ {
+ if(maY.isSet())
+ {
+ return maY;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const SvgNumber& ret = mpXLink->getY();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return maY;
+ }
+
+ const SvgNumber& SvgPatternNode::getWidth() const
+ {
+ if(maWidth.isSet())
+ {
+ return maWidth;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const SvgNumber& ret = mpXLink->getWidth();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return maWidth;
+ }
+
+ const SvgNumber& SvgPatternNode::getHeight() const
+ {
+ if(maHeight.isSet())
+ {
+ return maHeight;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ const SvgNumber& ret = mpXLink->getHeight();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return maHeight;
+ }
+
+ const SvgUnits* SvgPatternNode::getPatternUnits() const
+ {
+ if(moPatternUnits)
+ {
+ return &*moPatternUnits;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getPatternUnits();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ const SvgUnits* SvgPatternNode::getPatternContentUnits() const
+ {
+ if(moPatternContentUnits)
+ {
+ return &*moPatternContentUnits;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getPatternContentUnits();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return nullptr;
+ }
+
+ std::optional<basegfx::B2DHomMatrix> SvgPatternNode::getPatternTransform() const
+ {
+ if(mpaPatternTransform)
+ {
+ return mpaPatternTransform;
+ }
+
+ const_cast< SvgPatternNode* >(this)->tryToFindLink();
+
+ if (mpXLink && !mbResolvingLink)
+ {
+ mbResolvingLink = true;
+ auto ret = mpXLink->getPatternTransform();
+ mbResolvingLink = false;
+ return ret;
+ }
+
+ return std::nullopt;
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgpolynode.cxx b/svgio/source/svgreader/svgpolynode.cxx
new file mode 100644
index 0000000000..30ab7ed311
--- /dev/null
+++ b/svgio/source/svgreader/svgpolynode.cxx
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgpolynode.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+namespace svgio::svgreader
+{
+ SvgPolyNode::SvgPolyNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgPolyNode::~SvgPolyNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgPolyNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgPolyNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Points:
+ {
+ basegfx::B2DPolygon aPath;
+
+ if(basegfx::utils::importFromSvgPoints(aPath, aContent))
+ {
+ if(aPath.count())
+ {
+ if(getType() == SVGToken::Polygon)
+ {
+ aPath.setClosed(true);
+ }
+
+ setPolygon(aPath);
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgPolyNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(pStyle && mpPolygon)
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(basegfx::B2DPolyPolygon(*mpPolygon), aNewTarget, nullptr);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgrectnode.cxx b/svgio/source/svgreader/svgrectnode.cxx
new file mode 100644
index 0000000000..3829f21a67
--- /dev/null
+++ b/svgio/source/svgreader/svgrectnode.cxx
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgrectnode.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+namespace svgio::svgreader
+{
+ SvgRectNode::SvgRectNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Rect, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ maX(0),
+ maY(0),
+ maWidth(0),
+ maHeight(0)
+ {
+ }
+
+ SvgRectNode::~SvgRectNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgRectNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgRectNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Rx:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maRx = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Ry:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maRy = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgRectNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ // get size range and create path
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!(pStyle && getWidth().isSet() && getHeight().isSet()))
+ return;
+
+ const double fWidth(getWidth().solve(*this, NumberType::xcoordinate));
+ const double fHeight(getHeight().solve(*this, NumberType::ycoordinate));
+
+ if(fWidth <= 0.0 || fHeight <= 0.0)
+ return;
+
+ const double fX(getX().isSet() ? getX().solve(*this, NumberType::xcoordinate) : 0.0);
+ const double fY(getY().isSet() ? getY().solve(*this, NumberType::ycoordinate) : 0.0);
+ const basegfx::B2DRange aRange(fX, fY, fX + fWidth, fY + fHeight);
+ basegfx::B2DPolygon aPath;
+
+ if(getRx().isSet() || getRy().isSet())
+ {
+ double frX(getRx().isSet() ? getRx().solve(*this, NumberType::xcoordinate) : 0.0);
+ double frY(getRy().isSet() ? getRy().solve(*this, NumberType::ycoordinate) : 0.0);
+
+ if(!getRy().isSet() && 0.0 == frY && frX > 0.0)
+ {
+ frY = frX;
+ }
+ else if(!getRx().isSet() && 0.0 == frX && frY > 0.0)
+ {
+ frX = frY;
+ }
+
+ frX /= fWidth;
+ frY /= fHeight;
+
+ frX = std::min(0.5, frX);
+ frY = std::min(0.5, frY);
+
+ aPath = basegfx::utils::createPolygonFromRect(aRange, frX * 2.0, frY * 2.0);
+ }
+ else
+ {
+ aPath = basegfx::utils::createPolygonFromRect(aRange);
+ }
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ pStyle->add_path(basegfx::B2DPolyPolygon(aPath), aNewTarget, nullptr);
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgstyleattributes.cxx b/svgio/source/svgreader/svgstyleattributes.cxx
new file mode 100644
index 0000000000..869b071dad
--- /dev/null
+++ b/svgio/source/svgreader/svgstyleattributes.cxx
@@ -0,0 +1,3128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#include <svgstyleattributes.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
+#include <svgnode.hxx>
+#include <svgdocument.hxx>
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+#include <svggradientnode.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <basegfx/vector/b2enums.hxx>
+#include <drawinglayer/processor2d/linegeometryextractor2d.hxx>
+#include <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <svgclippathnode.hxx>
+#include <svgfilternode.hxx>
+#include <svgmasknode.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <svgmarkernode.hxx>
+#include <svgpatternnode.hxx>
+#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pagehierarchyprimitive2d.hxx>
+#include <o3tl/string_view.hxx>
+#include <o3tl/unit_conversion.hxx>
+
+const int nStyleDepthLimit = 1024;
+
+namespace svgio::svgreader
+{
+ static basegfx::B2DLineJoin StrokeLinejoinToB2DLineJoin(StrokeLinejoin aStrokeLinejoin)
+ {
+ if(StrokeLinejoin::round == aStrokeLinejoin)
+ {
+ return basegfx::B2DLineJoin::Round;
+ }
+ else if(StrokeLinejoin::bevel == aStrokeLinejoin)
+ {
+ return basegfx::B2DLineJoin::Bevel;
+ }
+
+ return basegfx::B2DLineJoin::Miter;
+ }
+
+ static css::drawing::LineCap StrokeLinecapToDrawingLineCap(StrokeLinecap aStrokeLinecap)
+ {
+ switch(aStrokeLinecap)
+ {
+ default: /* StrokeLinecap::notset, StrokeLinecap::butt */
+ {
+ return css::drawing::LineCap_BUTT;
+ }
+ case StrokeLinecap::round:
+ {
+ return css::drawing::LineCap_ROUND;
+ }
+ case StrokeLinecap::square:
+ {
+ return css::drawing::LineCap_SQUARE;
+ }
+ }
+ }
+
+ FontStretch getWider(FontStretch aSource)
+ {
+ switch(aSource)
+ {
+ case FontStretch::ultra_condensed: aSource = FontStretch::extra_condensed; break;
+ case FontStretch::extra_condensed: aSource = FontStretch::condensed; break;
+ case FontStretch::condensed: aSource = FontStretch::semi_condensed; break;
+ case FontStretch::semi_condensed: aSource = FontStretch::normal; break;
+ case FontStretch::normal: aSource = FontStretch::semi_expanded; break;
+ case FontStretch::semi_expanded: aSource = FontStretch::expanded; break;
+ case FontStretch::expanded: aSource = FontStretch::extra_expanded; break;
+ case FontStretch::extra_expanded: aSource = FontStretch::ultra_expanded; break;
+ default: break;
+ }
+
+ return aSource;
+ }
+
+ FontStretch getNarrower(FontStretch aSource)
+ {
+ switch(aSource)
+ {
+ case FontStretch::extra_condensed: aSource = FontStretch::ultra_condensed; break;
+ case FontStretch::condensed: aSource = FontStretch::extra_condensed; break;
+ case FontStretch::semi_condensed: aSource = FontStretch::condensed; break;
+ case FontStretch::normal: aSource = FontStretch::semi_condensed; break;
+ case FontStretch::semi_expanded: aSource = FontStretch::normal; break;
+ case FontStretch::expanded: aSource = FontStretch::semi_expanded; break;
+ case FontStretch::extra_expanded: aSource = FontStretch::expanded; break;
+ case FontStretch::ultra_expanded: aSource = FontStretch::extra_expanded; break;
+ default: break;
+ }
+
+ return aSource;
+ }
+
+ FontWeight getBolder(FontWeight aSource)
+ {
+ switch(aSource)
+ {
+ case FontWeight::N100: aSource = FontWeight::N200; break;
+ case FontWeight::N200: aSource = FontWeight::N300; break;
+ case FontWeight::N300: aSource = FontWeight::N400; break;
+ case FontWeight::N400: aSource = FontWeight::N500; break;
+ case FontWeight::N500: aSource = FontWeight::N600; break;
+ case FontWeight::N600: aSource = FontWeight::N700; break;
+ case FontWeight::N700: aSource = FontWeight::N800; break;
+ case FontWeight::N800: aSource = FontWeight::N900; break;
+ default: break;
+ }
+
+ return aSource;
+ }
+
+ FontWeight getLighter(FontWeight aSource)
+ {
+ switch(aSource)
+ {
+ case FontWeight::N200: aSource = FontWeight::N100; break;
+ case FontWeight::N300: aSource = FontWeight::N200; break;
+ case FontWeight::N400: aSource = FontWeight::N300; break;
+ case FontWeight::N500: aSource = FontWeight::N400; break;
+ case FontWeight::N600: aSource = FontWeight::N500; break;
+ case FontWeight::N700: aSource = FontWeight::N600; break;
+ case FontWeight::N800: aSource = FontWeight::N700; break;
+ case FontWeight::N900: aSource = FontWeight::N800; break;
+ default: break;
+ }
+
+ return aSource;
+ }
+
+ ::FontWeight getVclFontWeight(FontWeight aSource)
+ {
+ ::FontWeight nRetval(WEIGHT_NORMAL);
+
+ switch(aSource)
+ {
+ case FontWeight::N100: nRetval = WEIGHT_ULTRALIGHT; break;
+ case FontWeight::N200: nRetval = WEIGHT_LIGHT; break;
+ case FontWeight::N300: nRetval = WEIGHT_SEMILIGHT; break;
+ case FontWeight::N400: nRetval = WEIGHT_NORMAL; break;
+ case FontWeight::N500: nRetval = WEIGHT_MEDIUM; break;
+ case FontWeight::N600: nRetval = WEIGHT_SEMIBOLD; break;
+ case FontWeight::N700: nRetval = WEIGHT_BOLD; break;
+ case FontWeight::N800: nRetval = WEIGHT_ULTRABOLD; break;
+ case FontWeight::N900: nRetval = WEIGHT_BLACK; break;
+ default: break;
+ }
+
+ return nRetval;
+ }
+
+ void SvgStyleAttributes::readCssStyle(std::u16string_view rCandidate)
+ {
+ const sal_Int32 nLen(rCandidate.size());
+ sal_Int32 nPos(0);
+
+ while(nPos < nLen)
+ {
+ // get TokenName
+ OUStringBuffer aTokenName;
+ skip_char(rCandidate, u' ', nPos, nLen);
+ copyString(rCandidate, nPos, aTokenName, nLen);
+
+ if (aTokenName.isEmpty())
+ {
+ // if no TokenName advance one by force to avoid death loop, continue
+ OSL_ENSURE(false, "Could not interpret on current position, advancing one byte (!)");
+ nPos++;
+ continue;
+ }
+
+ // get TokenValue
+ OUStringBuffer aTokenValue;
+ skip_char(rCandidate, u' ', u':', nPos, nLen);
+ copyToLimiter(rCandidate, u';', nPos, aTokenValue, nLen);
+ skip_char(rCandidate, u' ', u';', nPos, nLen);
+
+ if (aTokenValue.isEmpty())
+ {
+ // no value - continue
+ continue;
+ }
+
+ // generate OUStrings
+ const OUString aOUTokenName(aTokenName.makeStringAndClear());
+ OUString aOUTokenValue(aTokenValue.makeStringAndClear());
+
+ // check for '!important' CssStyle mark, currently not supported
+ // but needs to be extracted for correct parsing
+ OUString aTokenImportant("!important");
+ const sal_Int32 nIndexTokenImportant(aOUTokenValue.indexOf(aTokenImportant));
+
+ if(-1 != nIndexTokenImportant)
+ {
+ // if there currently just remove it and remove spaces to have the value only
+ OUString aNewOUTokenValue;
+
+ if(nIndexTokenImportant > 0)
+ {
+ // copy content before token
+ aNewOUTokenValue += aOUTokenValue.subView(0, nIndexTokenImportant);
+ }
+
+ if(aOUTokenValue.getLength() > nIndexTokenImportant + aTokenImportant.getLength())
+ {
+ // copy content after token
+ aNewOUTokenValue += aOUTokenValue.subView(nIndexTokenImportant + aTokenImportant.getLength());
+ }
+
+ // remove spaces
+ aOUTokenValue = aNewOUTokenValue.trim();
+ }
+
+ // valid token-value pair, parse it
+ parseStyleAttribute(StrToSVGToken(aOUTokenName, true), aOUTokenValue);
+ }
+ }
+
+ const SvgStyleAttributes* SvgStyleAttributes::getParentStyle() const
+ {
+ if(getCssStyleParent())
+ {
+ return getCssStyleParent();
+ }
+
+ if(mrOwner.supportsParentStyle() && mrOwner.getParent())
+ {
+ return mrOwner.getParent()->getSvgStyleAttributes();
+ }
+
+ return nullptr;
+ }
+
+ void SvgStyleAttributes::add_text(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource) const
+ {
+ if(rSource.empty())
+ return;
+
+ // at this point the primitives in rSource are of type TextSimplePortionPrimitive2D
+ // or TextDecoratedPortionPrimitive2D and have the Fill Color (pAttributes->getFill())
+ // set. When another fill is used and also evtl. stroke is set it gets necessary to
+ // dismantle to geometry and add needed primitives
+ const basegfx::BColor* pFill = getFill();
+ const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
+ const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
+ const basegfx::BColor* pStroke = getStroke();
+ const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
+ const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
+ basegfx::B2DPolyPolygon aMergedArea;
+
+ if(pFillGradient || pFillPattern || pStroke || pStrokeGradient || pStrokePattern)
+ {
+ // text geometry is needed, create
+ // use neutral ViewInformation and create LineGeometryExtractor2D
+ const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
+ drawinglayer::processor2d::TextAsPolygonExtractor2D aExtractor(aViewInformation2D);
+
+ // process
+ aExtractor.process(rSource);
+
+ // get results
+ const drawinglayer::processor2d::TextAsPolygonDataNodeVector& rResult = aExtractor.getTarget();
+ const sal_uInt32 nResultCount(rResult.size());
+ basegfx::B2DPolyPolygonVector aTextFillVector;
+ aTextFillVector.reserve(nResultCount);
+
+ for(sal_uInt32 a(0); a < nResultCount; a++)
+ {
+ const drawinglayer::processor2d::TextAsPolygonDataNode& rCandidate = rResult[a];
+
+ if(rCandidate.getIsFilled())
+ {
+ aTextFillVector.push_back(rCandidate.getB2DPolyPolygon());
+ }
+ }
+
+ if(!aTextFillVector.empty())
+ {
+ aMergedArea = basegfx::utils::mergeToSinglePolyPolygon(aTextFillVector);
+ }
+ }
+
+ const bool bStrokeUsed(pStroke || pStrokeGradient || pStrokePattern);
+
+ // add fill. Use geometry even for simple color fill when stroke
+ // is used, else text rendering and the geometry-based stroke will
+ // normally not really match optically due to diverse system text
+ // renderers
+ if(aMergedArea.count() && (pFillGradient || pFillPattern || bStrokeUsed))
+ {
+ // create text fill content based on geometry
+ add_fill(aMergedArea, rTarget, aMergedArea.getB2DRange());
+ }
+ else if(pFill)
+ {
+ // add the already prepared primitives for single color fill
+ rTarget.append(std::move(rSource));
+ }
+
+ // add stroke
+ if(aMergedArea.count() && bStrokeUsed)
+ {
+ // create text stroke content
+ add_stroke(aMergedArea, rTarget, aMergedArea.getB2DRange());
+ }
+ }
+
+ void SvgStyleAttributes::add_fillGradient(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgGradientNode& rFillGradient,
+ const basegfx::B2DRange& rGeoRange) const
+ {
+ // create fill content
+ drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector;
+
+ // get the color stops
+ rFillGradient.collectGradientEntries(aSvgGradientEntryVector);
+
+ if(aSvgGradientEntryVector.empty())
+ return;
+
+ basegfx::B2DHomMatrix aGeoToUnit;
+ basegfx::B2DHomMatrix aGradientTransform;
+
+ if(rFillGradient.getGradientTransform())
+ {
+ aGradientTransform = *rFillGradient.getGradientTransform();
+ }
+
+ if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
+ {
+ aGeoToUnit.translate(-rGeoRange.getMinX(), -rGeoRange.getMinY());
+ aGeoToUnit.scale(1.0 / rGeoRange.getWidth(), 1.0 / rGeoRange.getHeight());
+ }
+
+ if(SVGToken::LinearGradient == rFillGradient.getType())
+ {
+ basegfx::B2DPoint aStart(0.0, 0.0);
+ basegfx::B2DPoint aEnd(1.0, 0.0);
+
+ if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
+ {
+ // all possible units
+ aStart.setX(rFillGradient.getX1().solve(mrOwner, NumberType::xcoordinate));
+ aStart.setY(rFillGradient.getY1().solve(mrOwner, NumberType::ycoordinate));
+ aEnd.setX(rFillGradient.getX2().solve(mrOwner, NumberType::xcoordinate));
+ aEnd.setY(rFillGradient.getY2().solve(mrOwner, NumberType::ycoordinate));
+ }
+ else
+ {
+ // fractions or percent relative to object bounds
+ const SvgNumber X1(rFillGradient.getX1());
+ const SvgNumber Y1(rFillGradient.getY1());
+ const SvgNumber X2(rFillGradient.getX2());
+ const SvgNumber Y2(rFillGradient.getY2());
+
+ aStart.setX(SvgUnit::percent == X1.getUnit() ? X1.getNumber() * 0.01 : X1.getNumber());
+ aStart.setY(SvgUnit::percent == Y1.getUnit() ? Y1.getNumber() * 0.01 : Y1.getNumber());
+ aEnd.setX(SvgUnit::percent == X2.getUnit() ? X2.getNumber() * 0.01 : X2.getNumber());
+ aEnd.setY(SvgUnit::percent == Y2.getUnit() ? Y2.getNumber() * 0.01 : Y2.getNumber());
+ }
+
+ if(!aGeoToUnit.isIdentity())
+ {
+ aStart *= aGeoToUnit;
+ aEnd *= aGeoToUnit;
+ }
+
+ rTarget.push_back(
+ new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
+ aGradientTransform,
+ rPath,
+ std::move(aSvgGradientEntryVector),
+ aStart,
+ aEnd,
+ SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
+ rFillGradient.getSpreadMethod()));
+ }
+ else
+ {
+ basegfx::B2DPoint aStart(0.5, 0.5);
+ basegfx::B2DPoint aFocal;
+ double fRadius(0.5);
+ const SvgNumber* pFx = rFillGradient.getFx();
+ const SvgNumber* pFy = rFillGradient.getFy();
+ const bool bFocal(pFx || pFy);
+
+ if (SvgUnits::userSpaceOnUse == rFillGradient.getGradientUnits())
+ {
+ // all possible units
+ aStart.setX(rFillGradient.getCx().solve(mrOwner, NumberType::xcoordinate));
+ aStart.setY(rFillGradient.getCy().solve(mrOwner, NumberType::ycoordinate));
+ fRadius = rFillGradient.getR().solve(mrOwner);
+
+ if(bFocal)
+ {
+ aFocal.setX(pFx ? pFx->solve(mrOwner, NumberType::xcoordinate) : aStart.getX());
+ aFocal.setY(pFy ? pFy->solve(mrOwner, NumberType::ycoordinate) : aStart.getY());
+ }
+ }
+ else
+ {
+ // fractions or percent relative to object bounds
+ const SvgNumber Cx(rFillGradient.getCx());
+ const SvgNumber Cy(rFillGradient.getCy());
+ const SvgNumber R(rFillGradient.getR());
+
+ aStart.setX(SvgUnit::percent == Cx.getUnit() ? Cx.getNumber() * 0.01 : Cx.getNumber());
+ aStart.setY(SvgUnit::percent == Cy.getUnit() ? Cy.getNumber() * 0.01 : Cy.getNumber());
+ fRadius = (SvgUnit::percent == R.getUnit()) ? R.getNumber() * 0.01 : R.getNumber();
+
+ if(bFocal)
+ {
+ aFocal.setX(pFx ? (SvgUnit::percent == pFx->getUnit() ? pFx->getNumber() * 0.01 : pFx->getNumber()) : aStart.getX());
+ aFocal.setY(pFy ? (SvgUnit::percent == pFy->getUnit() ? pFy->getNumber() * 0.01 : pFy->getNumber()) : aStart.getY());
+ }
+ }
+
+ if(!aGeoToUnit.isIdentity())
+ {
+ aStart *= aGeoToUnit;
+ fRadius = (aGeoToUnit * basegfx::B2DVector(fRadius, 0.0)).getLength();
+
+ if(bFocal)
+ {
+ aFocal *= aGeoToUnit;
+ }
+ }
+
+ rTarget.push_back(
+ new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
+ aGradientTransform,
+ rPath,
+ std::move(aSvgGradientEntryVector),
+ aStart,
+ fRadius,
+ SvgUnits::userSpaceOnUse != rFillGradient.getGradientUnits(),
+ rFillGradient.getSpreadMethod(),
+ bFocal ? &aFocal : nullptr));
+ }
+ }
+
+ void SvgStyleAttributes::add_fillPatternTransform(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgPatternNode& rFillPattern,
+ const basegfx::B2DRange& rGeoRange) const
+ {
+ // prepare fill polyPolygon with given pattern, check for patternTransform
+ if(rFillPattern.getPatternTransform() && !rFillPattern.getPatternTransform()->isIdentity())
+ {
+ // PatternTransform is active; Handle by filling the inverse transformed
+ // path and back-transforming the result
+ basegfx::B2DPolyPolygon aPath(rPath);
+ basegfx::B2DHomMatrix aInv(*rFillPattern.getPatternTransform());
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ aInv.invert();
+ aPath.transform(aInv);
+ add_fillPattern(aPath, aNewTarget, rFillPattern, aPath.getB2DRange());
+
+ if(!aNewTarget.empty())
+ {
+ rTarget.push_back(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ *rFillPattern.getPatternTransform(),
+ std::move(aNewTarget)));
+ }
+ }
+ else
+ {
+ // no patternTransform, create fillPattern directly
+ add_fillPattern(rPath, rTarget, rFillPattern, rGeoRange);
+ }
+ }
+
+ void SvgStyleAttributes::add_fillPattern(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const SvgPatternNode& rFillPattern,
+ const basegfx::B2DRange& rGeoRange) const
+ {
+ // fill polyPolygon with given pattern
+ const drawinglayer::primitive2d::Primitive2DContainer& rPrimitives = rFillPattern.getPatternPrimitives();
+
+ if(rPrimitives.empty())
+ return;
+
+ double fTargetWidth(rGeoRange.getWidth());
+ double fTargetHeight(rGeoRange.getHeight());
+
+ if(fTargetWidth <= 0.0 || fTargetHeight <= 0.0)
+ return;
+
+ // get relative values from pattern
+ double fX(0.0);
+ double fY(0.0);
+ double fW(0.0);
+ double fH(0.0);
+
+ rFillPattern.getValuesRelative(fX, fY, fW, fH, rGeoRange, mrOwner);
+
+ if(fW <= 0.0 || fH <= 0.0)
+ return;
+
+ // build the reference range relative to the rGeoRange
+ const basegfx::B2DRange aReferenceRange(fX, fY, fX + fW, fY + fH);
+
+ // find out how the content is mapped to the reference range
+ basegfx::B2DHomMatrix aMapPrimitivesToUnitRange;
+ const basegfx::B2DRange* pViewBox = rFillPattern.getViewBox();
+
+ if(pViewBox)
+ {
+ // use viewBox/preserveAspectRatio
+ const SvgAspectRatio& rRatio = rFillPattern.getSvgAspectRatio();
+ const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
+
+ if(rRatio.isSet())
+ {
+ // let mapping be created from SvgAspectRatio
+ aMapPrimitivesToUnitRange = rRatio.createMapping(aUnitRange, *pViewBox);
+ }
+ else
+ {
+ // choose default mapping
+ aMapPrimitivesToUnitRange = SvgAspectRatio::createLinearMapping(aUnitRange, *pViewBox);
+ }
+ }
+ else
+ {
+ // use patternContentUnits
+ const SvgUnits aPatternContentUnits(rFillPattern.getPatternContentUnits() ? *rFillPattern.getPatternContentUnits() : SvgUnits::userSpaceOnUse);
+
+ if (SvgUnits::userSpaceOnUse == aPatternContentUnits)
+ {
+ // create relative mapping to unit coordinates
+ aMapPrimitivesToUnitRange.scale(1.0 / (fW * fTargetWidth), 1.0 / (fH * fTargetHeight));
+ }
+ else
+ {
+ aMapPrimitivesToUnitRange.scale(1.0 / fW, 1.0 / fH);
+ }
+ }
+
+ // apply aMapPrimitivesToUnitRange to content when used
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rPrimitives);
+
+ if(!aMapPrimitivesToUnitRange.isIdentity())
+ {
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aMapPrimitivesToUnitRange,
+ std::move(aPrimitives)));
+
+ aPrimitives = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+
+ // embed in PatternFillPrimitive2D
+ rTarget.push_back(
+ new drawinglayer::primitive2d::PatternFillPrimitive2D(
+ rPath,
+ std::move(aPrimitives),
+ aReferenceRange));
+ }
+
+ void SvgStyleAttributes::add_fill(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DRange& rGeoRange) const
+ {
+ const basegfx::BColor* pFill = getFill();
+ const SvgGradientNode* pFillGradient = getSvgGradientNodeFill();
+ const SvgPatternNode* pFillPattern = getSvgPatternNodeFill();
+
+ if(!(pFill || pFillGradient || pFillPattern))
+ return;
+
+ const double fFillOpacity(getFillOpacity().solve(mrOwner));
+
+ if(!basegfx::fTools::more(fFillOpacity, 0.0))
+ return;
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewFill;
+
+ if(pFillGradient)
+ {
+ // create fill content with SVG gradient primitive
+ add_fillGradient(rPath, aNewFill, *pFillGradient, rGeoRange);
+ }
+ else if(pFillPattern)
+ {
+ // create fill content with SVG pattern primitive
+ add_fillPatternTransform(rPath, aNewFill, *pFillPattern, rGeoRange);
+ }
+ else // if(pFill)
+ {
+ // create fill content
+ aNewFill.resize(1);
+ aNewFill[0] = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ rPath,
+ *pFill);
+ }
+
+ if(aNewFill.empty())
+ return;
+
+ if(basegfx::fTools::less(fFillOpacity, 1.0))
+ {
+ // embed in UnifiedTransparencePrimitive2D
+ rTarget.push_back(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ std::move(aNewFill),
+ 1.0 - fFillOpacity));
+ }
+ else
+ {
+ // append
+ rTarget.append(aNewFill);
+ }
+ }
+
+ void SvgStyleAttributes::add_stroke(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DRange& rGeoRange) const
+ {
+ const basegfx::BColor* pStroke = getStroke();
+ const SvgGradientNode* pStrokeGradient = getSvgGradientNodeStroke();
+ const SvgPatternNode* pStrokePattern = getSvgPatternNodeStroke();
+
+ if(!(pStroke || pStrokeGradient || pStrokePattern))
+ return;
+
+ drawinglayer::primitive2d::Primitive2DContainer aNewStroke;
+ const double fStrokeOpacity(getStrokeOpacity().solve(mrOwner));
+
+ if(!basegfx::fTools::more(fStrokeOpacity, 0.0))
+ return;
+
+ // get stroke width; SVG does not use 0.0 == hairline, so 0.0 is no line at all
+ const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
+
+ if(!basegfx::fTools::more(fStrokeWidth, 0.0))
+ return;
+
+ drawinglayer::primitive2d::Primitive2DReference aNewLinePrimitive;
+
+ // if we have a line with two identical points it is not really a line,
+ // but used by SVG sometimes to paint a single dot.In that case, create
+ // the geometry for a single dot
+ if(1 == rPath.count())
+ {
+ const basegfx::B2DPolygon& aSingle(rPath.getB2DPolygon(0));
+
+ if(2 == aSingle.count() && aSingle.getB2DPoint(0).equal(aSingle.getB2DPoint(1)))
+ {
+ aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromCircle(
+ aSingle.getB2DPoint(0),
+ fStrokeWidth * (1.44 * 0.5))),
+ pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0));
+ }
+ }
+
+ if(!aNewLinePrimitive.is())
+ {
+ // get LineJoin, LineCap and stroke array
+ const basegfx::B2DLineJoin aB2DLineJoin(StrokeLinejoinToB2DLineJoin(getStrokeLinejoin()));
+ const css::drawing::LineCap aLineCap(StrokeLinecapToDrawingLineCap(getStrokeLinecap()));
+ ::std::vector< double > aDashArray;
+
+ if(!getStrokeDasharray().empty())
+ {
+ aDashArray = solveSvgNumberVector(getStrokeDasharray(), mrOwner);
+ }
+
+ // convert svg:stroke-miterlimit to LineAttrute:mfMiterMinimumAngle
+ // The default needs to be set explicitly, because svg default <> Draw default
+ double fMiterMinimumAngle;
+ if (getStrokeMiterLimit().isSet())
+ {
+ fMiterMinimumAngle = 2.0 * asin(1.0/getStrokeMiterLimit().getNumber());
+ }
+ else
+ {
+ fMiterMinimumAngle = 2.0 * asin(0.25); // 1.0/default 4.0
+ }
+
+ // todo: Handle getStrokeDashOffset()
+
+ // prepare line attribute
+ const drawinglayer::attribute::LineAttribute aLineAttribute(
+ pStroke ? *pStroke : basegfx::BColor(0.0, 0.0, 0.0),
+ fStrokeWidth,
+ aB2DLineJoin,
+ aLineCap,
+ fMiterMinimumAngle);
+
+ if(aDashArray.empty())
+ {
+ aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
+ rPath,
+ aLineAttribute);
+ }
+ else
+ {
+ drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashArray));
+
+ aNewLinePrimitive = new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
+ rPath,
+ aLineAttribute,
+ std::move(aStrokeAttribute));
+ }
+ }
+
+ if(pStrokeGradient || pStrokePattern)
+ {
+ // put primitive into Primitive2DReference and Primitive2DSequence
+ const drawinglayer::primitive2d::Primitive2DContainer aSeq { aNewLinePrimitive };
+
+ // use neutral ViewInformation and create LineGeometryExtractor2D
+ const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
+ drawinglayer::processor2d::LineGeometryExtractor2D aExtractor(aViewInformation2D);
+
+ // process
+ aExtractor.process(aSeq);
+
+ // check for fill rsults
+ const basegfx::B2DPolyPolygonVector& rLineFillVector(aExtractor.getExtractedLineFills());
+
+ if(!rLineFillVector.empty())
+ {
+ const basegfx::B2DPolyPolygon aMergedArea(
+ basegfx::utils::mergeToSinglePolyPolygon(
+ rLineFillVector));
+
+ if(aMergedArea.count())
+ {
+ if(pStrokeGradient)
+ {
+ // create fill content with SVG gradient primitive. Use original GeoRange,
+ // e.g. from circle without LineWidth
+ add_fillGradient(aMergedArea, aNewStroke, *pStrokeGradient, rGeoRange);
+ }
+ else // if(pStrokePattern)
+ {
+ // create fill content with SVG pattern primitive. Use GeoRange
+ // from the expanded data, e.g. circle with extended geo by half linewidth
+ add_fillPatternTransform(aMergedArea, aNewStroke, *pStrokePattern, aMergedArea.getB2DRange());
+ }
+ }
+ }
+ }
+ else // if(pStroke)
+ {
+ aNewStroke.push_back(aNewLinePrimitive);
+ }
+
+ if(aNewStroke.empty())
+ return;
+
+ if(basegfx::fTools::less(fStrokeOpacity, 1.0))
+ {
+ // embed in UnifiedTransparencePrimitive2D
+ rTarget.push_back(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ std::move(aNewStroke),
+ 1.0 - fStrokeOpacity));
+ }
+ else
+ {
+ // append
+ rTarget.append(aNewStroke);
+ }
+ }
+
+ bool SvgStyleAttributes::prepare_singleMarker(
+ drawinglayer::primitive2d::Primitive2DContainer& rMarkerPrimitives,
+ basegfx::B2DHomMatrix& rMarkerTransform,
+ basegfx::B2DRange& rClipRange,
+ const SvgMarkerNode& rMarker) const
+ {
+ // reset return values
+ rMarkerTransform.identity();
+ rClipRange.reset();
+
+ // get marker primitive representation
+ rMarkerPrimitives = rMarker.getMarkerPrimitives();
+
+ if(!rMarkerPrimitives.empty())
+ {
+ basegfx::B2DRange aPrimitiveRange(0.0, 0.0, 1.0, 1.0);
+ const basegfx::B2DRange* pViewBox = rMarker.getViewBox();
+
+ if(pViewBox)
+ {
+ aPrimitiveRange = *pViewBox;
+ }
+
+ if(aPrimitiveRange.getWidth() > 0.0 && aPrimitiveRange.getHeight() > 0.0)
+ {
+ double fTargetWidth(rMarker.getMarkerWidth().isSet() ? rMarker.getMarkerWidth().solve(mrOwner, NumberType::xcoordinate) : 3.0);
+ double fTargetHeight(rMarker.getMarkerHeight().isSet() ? rMarker.getMarkerHeight().solve(mrOwner, NumberType::xcoordinate) : 3.0);
+ const bool bStrokeWidth(SvgMarkerNode::MarkerUnits::strokeWidth == rMarker.getMarkerUnits());
+ const double fStrokeWidth(getStrokeWidth().isSet() ? getStrokeWidth().solve(mrOwner) : 1.0);
+
+ if(bStrokeWidth)
+ {
+ // relative to strokeWidth
+ fTargetWidth *= fStrokeWidth;
+ fTargetHeight *= fStrokeWidth;
+ }
+
+ if(fTargetWidth > 0.0 && fTargetHeight > 0.0)
+ {
+ // create mapping
+ const basegfx::B2DRange aTargetRange(0.0, 0.0, fTargetWidth, fTargetHeight);
+ const SvgAspectRatio& rRatio = rMarker.getSvgAspectRatio();
+
+ if(rRatio.isSet())
+ {
+ // let mapping be created from SvgAspectRatio
+ rMarkerTransform = rRatio.createMapping(aTargetRange, aPrimitiveRange);
+
+ if(rRatio.isMeetOrSlice())
+ {
+ // need to clip
+ rClipRange = aPrimitiveRange;
+ }
+ }
+ else
+ {
+ if(!pViewBox)
+ {
+ if(bStrokeWidth)
+ {
+ // adapt to strokewidth if needed
+ rMarkerTransform.scale(fStrokeWidth, fStrokeWidth);
+ }
+ }
+ else
+ {
+ // choose default mapping
+ rMarkerTransform = SvgAspectRatio::createLinearMapping(aTargetRange, aPrimitiveRange);
+ }
+ }
+
+ // get and apply reference point. Initially it's in marker local coordinate system
+ basegfx::B2DPoint aRefPoint(
+ rMarker.getRefX().isSet() ? rMarker.getRefX().solve(mrOwner, NumberType::xcoordinate) : 0.0,
+ rMarker.getRefY().isSet() ? rMarker.getRefY().solve(mrOwner, NumberType::ycoordinate) : 0.0);
+
+ // apply MarkerTransform to have it in mapped coordinates
+ aRefPoint *= rMarkerTransform;
+
+ // apply by moving RepPoint to (0.0)
+ rMarkerTransform.translate(-aRefPoint.getX(), -aRefPoint.getY());
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void SvgStyleAttributes::add_markers(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::utils::PointIndexSet* pHelpPointIndices) const
+ {
+ // try to access linked markers
+ const SvgMarkerNode* pStart = accessMarkerStartXLink();
+ const SvgMarkerNode* pMid = accessMarkerMidXLink();
+ const SvgMarkerNode* pEnd = accessMarkerEndXLink();
+
+ if(!(pStart || pMid || pEnd))
+ return;
+
+ const sal_uInt32 nSubPathCount(rPath.count());
+
+ if(!nSubPathCount)
+ return;
+
+ // remember prepared marker; pStart, pMid and pEnd may all be equal when
+ // only 'marker' was used instead of 'marker-start', 'marker-mid' or 'marker-end',
+ // see 'case SVGToken::Marker' in this file; thus in this case only one common
+ // marker in primitive form will be prepared
+ const SvgMarkerNode* pPrepared = nullptr;
+
+ // values for the prepared marker, results of prepare_singleMarker
+ drawinglayer::primitive2d::Primitive2DContainer aPreparedMarkerPrimitives;
+ basegfx::B2DHomMatrix aPreparedMarkerTransform;
+ basegfx::B2DRange aPreparedMarkerClipRange;
+
+ for (sal_uInt32 a(0); a < nSubPathCount; a++)
+ {
+ // iterate over sub-paths
+ const basegfx::B2DPolygon& aSubPolygonPath(rPath.getB2DPolygon(a));
+ const sal_uInt32 nSubPolygonPointCount(aSubPolygonPath.count());
+ const bool bSubPolygonPathIsClosed(aSubPolygonPath.isClosed());
+
+ if(nSubPolygonPointCount)
+ {
+ // for each sub-path, create one marker per point (when closed, two markers
+ // need to pe created for the 1st point)
+ const sal_uInt32 nTargetMarkerCount(bSubPolygonPathIsClosed ? nSubPolygonPointCount + 1 : nSubPolygonPointCount);
+
+ for (sal_uInt32 b(0); b < nTargetMarkerCount; b++)
+ {
+ const bool bIsFirstMarker(!a && !b);
+ const bool bIsLastMarker(nSubPathCount - 1 == a && nTargetMarkerCount - 1 == b);
+ const SvgMarkerNode* pNeeded = nullptr;
+
+ if(bIsFirstMarker)
+ {
+ // 1st point in 1st sub-polygon, use pStart
+ pNeeded = pStart;
+ }
+ else if(bIsLastMarker)
+ {
+ // last point in last sub-polygon, use pEnd
+ pNeeded = pEnd;
+ }
+ else
+ {
+ // anything in-between, use pMid
+ pNeeded = pMid;
+ }
+
+ if(pHelpPointIndices && !pHelpPointIndices->empty())
+ {
+ const basegfx::utils::PointIndexSet::const_iterator aFound(
+ pHelpPointIndices->find(basegfx::utils::PointIndex(a, b)));
+
+ if(aFound != pHelpPointIndices->end())
+ {
+ // this point is a pure helper point; do not create a marker for it
+ continue;
+ }
+ }
+
+ if(!pNeeded)
+ {
+ // no marker needs to be created for this point
+ continue;
+ }
+
+ if(pPrepared != pNeeded)
+ {
+ // if needed marker is not yet prepared, do it now
+ if(prepare_singleMarker(aPreparedMarkerPrimitives, aPreparedMarkerTransform, aPreparedMarkerClipRange, *pNeeded))
+ {
+ pPrepared = pNeeded;
+ }
+ else
+ {
+ // error: could not prepare given marker
+ OSL_ENSURE(false, "OOps, could not prepare given marker as primitives (!)");
+ pPrepared = nullptr;
+ continue;
+ }
+ }
+
+ // prepare complete transform
+ basegfx::B2DHomMatrix aCombinedTransform(aPreparedMarkerTransform);
+
+ // get rotation
+ if(pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start ||
+ pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
+ {
+ const sal_uInt32 nPointIndex(b % nSubPolygonPointCount);
+
+ // get entering and leaving tangents; this will search backward/forward
+ // in the polygon to find tangents unequal to zero, skipping empty edges
+ // see basegfx descriptions)
+ // Hint: Mozilla, Inkscape and others use only leaving tangent for start marker
+ // and entering tangent for end marker. To achieve this (if wanted) it is possible
+ // to make the fetch of aEntering/aLeaving dependent on bIsFirstMarker/bIsLastMarker.
+ // This is not done here, see comment 14 in task #1232379#
+ // or http://www.w3.org/TR/SVG/painting.html#OrientAttribute
+ basegfx::B2DVector aEntering(
+ basegfx::utils::getTangentEnteringPoint(
+ aSubPolygonPath,
+ nPointIndex));
+ basegfx::B2DVector aLeaving(
+ basegfx::utils::getTangentLeavingPoint(
+ aSubPolygonPath,
+ nPointIndex));
+ const bool bEntering(!aEntering.equalZero());
+ const bool bLeaving(!aLeaving.equalZero());
+
+ if(bEntering || bLeaving)
+ {
+ basegfx::B2DVector aSum(0.0, 0.0);
+
+ if(bEntering)
+ {
+ if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
+ aSum -= aEntering.normalize();
+ else
+ aSum += aEntering.normalize();
+ }
+
+ if(bLeaving)
+ {
+ if(bIsFirstMarker && pPrepared->getMarkerOrient() == SvgMarkerNode::MarkerOrient::auto_start_reverse)
+ aSum -= aLeaving.normalize();
+ else
+ aSum += aLeaving.normalize();
+ }
+
+ if(!aSum.equalZero())
+ {
+ const double fAngle(atan2(aSum.getY(), aSum.getX()));
+
+ // apply rotation
+ aCombinedTransform.rotate(fAngle);
+ }
+ }
+ }
+ else
+ {
+ // apply rotation
+ aCombinedTransform.rotate(pPrepared->getAngle());
+ }
+
+ // get and apply target position
+ const basegfx::B2DPoint aPoint(aSubPolygonPath.getB2DPoint(b % nSubPolygonPointCount));
+
+ aCombinedTransform.translate(aPoint.getX(), aPoint.getY());
+
+ // prepare marker
+ drawinglayer::primitive2d::Primitive2DReference xMarker(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aCombinedTransform,
+ drawinglayer::primitive2d::Primitive2DContainer(aPreparedMarkerPrimitives)));
+
+ if(!aPreparedMarkerClipRange.isEmpty())
+ {
+ // marker needs to be clipped, it's bigger as the mapping
+ basegfx::B2DPolyPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aPreparedMarkerClipRange));
+
+ aClipPolygon.transform(aCombinedTransform);
+ xMarker = new drawinglayer::primitive2d::MaskPrimitive2D(
+ std::move(aClipPolygon),
+ drawinglayer::primitive2d::Primitive2DContainer { xMarker });
+ }
+
+ // add marker
+ rTarget.push_back(xMarker);
+ }
+ }
+ }
+ }
+
+ void SvgStyleAttributes::add_path(
+ const basegfx::B2DPolyPolygon& rPath,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::utils::PointIndexSet* pHelpPointIndices) const
+ {
+ if(!rPath.count())
+ {
+ // no geometry at all
+ return;
+ }
+
+ const basegfx::B2DRange aGeoRange(rPath.getB2DRange());
+
+ if(aGeoRange.isEmpty())
+ {
+ // no geometry range
+ return;
+ }
+
+ const double fOpacity(getOpacity().solve(mrOwner));
+
+ if(basegfx::fTools::equalZero(fOpacity))
+ {
+ // not visible
+ return;
+ }
+
+ // check if it's a line
+ const bool bNoWidth(basegfx::fTools::equalZero(aGeoRange.getWidth()));
+ const bool bNoHeight(basegfx::fTools::equalZero(aGeoRange.getHeight()));
+ const bool bIsTwoPointLine(1 == rPath.count()
+ && !rPath.areControlPointsUsed()
+ && 2 == rPath.getB2DPolygon(0).count());
+ const bool bIsLine(bIsTwoPointLine || bNoWidth || bNoHeight);
+
+ if(!bIsLine)
+ {
+ // create fill
+ basegfx::B2DPolyPolygon aPath(rPath);
+
+ if(SVGToken::Path == mrOwner.getType() || SVGToken::Polygon == mrOwner.getType())
+ {
+ if(FillRule::evenodd != getClipRule() && FillRule::evenodd != getFillRule())
+ {
+ if(getFill() || getSvgGradientNodeFill() || getSvgPatternNodeFill())
+ {
+ // nonzero is wanted, solve geometrically (see description on basegfx)
+ // basegfx::utils::createNonzeroConform() is expensive for huge paths
+ // and is only needed if path will be filled later on
+ aPath = basegfx::utils::createNonzeroConform(aPath);
+ }
+ }
+ }
+
+ add_fill(aPath, rTarget, aGeoRange);
+ }
+
+ // create stroke
+ add_stroke(rPath, rTarget, aGeoRange);
+
+ // Svg supports markers for path, polygon, polyline and line
+ if(SVGToken::Path == mrOwner.getType() || // path
+ SVGToken::Polygon == mrOwner.getType() || // polygon
+ SVGToken::Polyline == mrOwner.getType() || // polyline
+ SVGToken::Line == mrOwner.getType() || // line
+ SVGToken::Style == mrOwner.getType()) // tdf#150323
+ {
+ // try to add markers
+ add_markers(rPath, rTarget, pHelpPointIndices);
+ }
+ }
+
+ void SvgStyleAttributes::add_postProcess(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource,
+ const std::optional<basegfx::B2DHomMatrix>& pTransform) const
+ {
+ const double fOpacity(getOpacity().solve(mrOwner));
+
+ if(basegfx::fTools::equalZero(fOpacity))
+ {
+ return;
+ }
+
+ drawinglayer::primitive2d::Primitive2DContainer aSource(std::move(rSource));
+
+ if(basegfx::fTools::less(fOpacity, 1.0))
+ {
+ // embed in UnifiedTransparencePrimitive2D
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ std::move(aSource),
+ 1.0 - fOpacity));
+
+ aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+
+ if(pTransform)
+ {
+ // create embedding group element with transformation. This applies the given
+ // transformation to the graphical content, but *not* to mask and/or clip (as needed)
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ *pTransform,
+ std::move(aSource)));
+
+ aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+
+ const SvgClipPathNode* pClip = accessClipPathXLink();
+ while(pClip)
+ {
+ // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
+ pClip->apply(aSource, pTransform);
+ pClip = pClip->getSvgStyleAttributes()->accessClipPathXLink();
+ }
+
+ if(!aSource.empty()) // test again, applied clipPath may have lead to empty geometry
+ {
+ const SvgFilterNode* pFilter = accessFilterXLink();
+ if(pFilter)
+ {
+ pFilter->apply(aSource);
+ }
+ }
+
+ if(!aSource.empty()) // test again, applied filter may have lead to empty geometry
+ {
+
+ const SvgMaskNode* pMask = accessMaskXLink();
+ if(pMask)
+ {
+ // #i124852# transform may be needed when SvgUnits::userSpaceOnUse
+ pMask->apply(aSource, pTransform);
+ }
+ }
+
+ // This is part of the SVG import of self-written SVGs from
+ // Draw/Impress containing multiple Slides/Pages. To be able
+ // to later 'break' these to multiple Pages if wanted, embed
+ // each Page-Content in an identifiable Primitive Grouping
+ // Object.
+ // This is the case when the current Node is a GroupNode, has
+ // class="Page" set, has a parent that also is a GroupNode
+ // at which class="Slide" is set.
+ // Multiple Slides/Pages are possible for Draw and Impress.
+ if(SVGToken::G == mrOwner.getType() && mrOwner.getClass())
+ {
+ const OUString aOwnerClass(*mrOwner.getClass());
+
+ if("Page" == aOwnerClass)
+ {
+ const SvgNode* pParent(mrOwner.getParent());
+
+ if(nullptr != pParent && SVGToken::G == pParent->getType() && pParent->getClass())
+ {
+ const OUString aParentClass(*pParent->getClass());
+
+ if("Slide" == aParentClass)
+ {
+ // embed to grouping primitive to identify the
+ // Slide/Page information
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::PageHierarchyPrimitive2D(
+ std::move(aSource)));
+
+ aSource = drawinglayer::primitive2d::Primitive2DContainer { xRef };
+ }
+ }
+ }
+ }
+
+ if(!aSource.empty()) // test again, applied mask may have lead to empty geometry
+ {
+ // append to current target
+ rTarget.append(aSource);
+ }
+ }
+
+ SvgStyleAttributes::SvgStyleAttributes(SvgNode& rOwner)
+ : mrOwner(rOwner),
+ mpCssStyleParent(nullptr),
+ maStopColor(basegfx::BColor(0.0, 0.0, 0.0), true),
+ maStrokeLinecap(StrokeLinecap::notset),
+ maStrokeLinejoin(StrokeLinejoin::notset),
+ maFontSize(),
+ maFontStretch(FontStretch::notset),
+ maFontStyle(FontStyle::notset),
+ maFontWeight(FontWeight::notset),
+ maTextAlign(TextAlign::notset),
+ maTextDecoration(TextDecoration::notset),
+ maTextAnchor(TextAnchor::notset),
+ maVisibility(Visibility::notset),
+ maFillRule(FillRule::notset),
+ maClipRule(FillRule::notset),
+ maBaselineShift(BaselineShift::Baseline),
+ maBaselineShiftNumber(0),
+ maDominantBaseline(DominantBaseline::Auto),
+ maResolvingParent(31, 0),
+ mbIsClipPathContent(SVGToken::ClipPathNode == mrOwner.getType()),
+ mbStrokeDasharraySet(false)
+ {
+ const SvgStyleAttributes* pParentStyle = getParentStyle();
+ if(!mbIsClipPathContent)
+ {
+ if(pParentStyle)
+ {
+ mbIsClipPathContent = pParentStyle->mbIsClipPathContent;
+ }
+ }
+ }
+
+ SvgStyleAttributes::~SvgStyleAttributes()
+ {
+ }
+
+ void SvgStyleAttributes::parseStyleAttribute(
+ SVGToken aSVGToken,
+ const OUString& aContent)
+ {
+ switch(aSVGToken)
+ {
+ case SVGToken::Fill:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ setFill(aSvgPaint);
+ if(aOpacity.isSet())
+ {
+ setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
+ }
+ }
+ else if(!aURL.isEmpty())
+ {
+ maNodeFillURL = aURL;
+ }
+ break;
+ }
+ case SVGToken::FillOpacity:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maFillOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
+ }
+ break;
+ }
+ case SVGToken::FillRule:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrNonzero))
+ {
+ maFillRule = FillRule::nonzero;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrEvenOdd))
+ {
+ maFillRule = FillRule::evenodd;
+ }
+ }
+ break;
+ }
+ case SVGToken::Stroke:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ maStroke = aSvgPaint;
+ if(aOpacity.isSet())
+ {
+ setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
+ }
+ }
+ else if(!aURL.isEmpty())
+ {
+ maNodeStrokeURL = aURL;
+ }
+ break;
+ }
+ case SVGToken::StrokeDasharray:
+ {
+ if(!aContent.isEmpty())
+ {
+ SvgNumberVector aVector;
+
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
+ {
+ // #121221# The special value 'none' needs to be handled
+ // in the sense that *when* it is set, the parent shall not
+ // be used. Before this was only dependent on the array being
+ // empty
+ mbStrokeDasharraySet = true;
+ }
+ else if(readSvgNumberVector(aContent, aVector))
+ {
+ maStrokeDasharray = aVector;
+ }
+ }
+ break;
+ }
+ case SVGToken::StrokeDashoffset:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maStrokeDashOffset = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::StrokeLinecap:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"butt"))
+ {
+ setStrokeLinecap(StrokeLinecap::butt);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"round"))
+ {
+ setStrokeLinecap(StrokeLinecap::round);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"square"))
+ {
+ setStrokeLinecap(StrokeLinecap::square);
+ }
+ }
+ break;
+ }
+ case SVGToken::StrokeLinejoin:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"miter"))
+ {
+ setStrokeLinejoin(StrokeLinejoin::miter);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"round"))
+ {
+ setStrokeLinejoin(StrokeLinejoin::round);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bevel"))
+ {
+ setStrokeLinejoin(StrokeLinejoin::bevel);
+ }
+ }
+ break;
+ }
+ case SVGToken::StrokeMiterlimit:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(basegfx::fTools::moreOrEqual(aNum.getNumber(), 1.0))
+ { //readSingleNumber sets SvgUnit::px as default, if unit is missing. Correct it here.
+ maStrokeMiterLimit = SvgNumber(aNum.getNumber(), SvgUnit::none);
+ }
+ }
+ break;
+ }
+ case SVGToken::StrokeOpacity:
+ {
+
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maStrokeOpacity = SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet());
+ }
+ break;
+ }
+ case SVGToken::StrokeWidth:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maStrokeWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::StopColor:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ maStopColor = aSvgPaint;
+ if(aOpacity.isSet())
+ {
+ setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
+ }
+ }
+ break;
+ }
+ case SVGToken::StopOpacity:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maStopOpacity = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Font:
+ {
+ break;
+ }
+ case SVGToken::FontFamily:
+ {
+ SvgStringVector aSvgStringVector;
+
+ if(readSvgStringVector(aContent, aSvgStringVector))
+ {
+ maFontFamily = aSvgStringVector;
+ }
+ break;
+ }
+ case SVGToken::FontSize:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"xx-small"))
+ {
+ setFontSize(FontSize::xx_small);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"x-small"))
+ {
+ setFontSize(FontSize::x_small);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"small"))
+ {
+ setFontSize(FontSize::small);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"smaller"))
+ {
+ setFontSize(FontSize::smaller);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"medium"))
+ {
+ setFontSize(FontSize::medium);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"larger"))
+ {
+ setFontSize(FontSize::larger);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"large"))
+ {
+ setFontSize(FontSize::large);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"x-large"))
+ {
+ setFontSize(FontSize::x_large);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"xx-large"))
+ {
+ setFontSize(FontSize::xx_large);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"initial"))
+ {
+ setFontSize(FontSize::initial);
+ }
+ else
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maFontSizeNumber = aNum;
+ }
+ }
+ }
+ break;
+ }
+ case SVGToken::FontSizeAdjust:
+ {
+ break;
+ }
+ case SVGToken::FontStretch:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
+ {
+ setFontStretch(FontStretch::normal);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"wider"))
+ {
+ setFontStretch(FontStretch::wider);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"narrower"))
+ {
+ setFontStretch(FontStretch::narrower);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"ultra-condensed"))
+ {
+ setFontStretch(FontStretch::ultra_condensed);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"extra-condensed"))
+ {
+ setFontStretch(FontStretch::extra_condensed);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"condensed"))
+ {
+ setFontStretch(FontStretch::condensed);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"semi-condensed"))
+ {
+ setFontStretch(FontStretch::semi_condensed);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"semi-expanded"))
+ {
+ setFontStretch(FontStretch::semi_expanded);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"expanded"))
+ {
+ setFontStretch(FontStretch::expanded);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"extra-expanded"))
+ {
+ setFontStretch(FontStretch::extra_expanded);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"ultra-expanded"))
+ {
+ setFontStretch(FontStretch::ultra_expanded);
+ }
+ }
+ break;
+ }
+ case SVGToken::FontStyle:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
+ {
+ setFontStyle(FontStyle::normal);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"italic"))
+ {
+ setFontStyle(FontStyle::italic);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"oblique"))
+ {
+ setFontStyle(FontStyle::oblique);
+ }
+ }
+ break;
+ }
+ case SVGToken::FontVariant:
+ {
+ break;
+ }
+ case SVGToken::FontWeight:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"100"))
+ {
+ setFontWeight(FontWeight::N100);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"200"))
+ {
+ setFontWeight(FontWeight::N200);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"300"))
+ {
+ setFontWeight(FontWeight::N300);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"400") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"normal"))
+ {
+ setFontWeight(FontWeight::N400);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"500"))
+ {
+ setFontWeight(FontWeight::N500);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"600"))
+ {
+ setFontWeight(FontWeight::N600);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"700") || o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bold"))
+ {
+ setFontWeight(FontWeight::N700);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"800"))
+ {
+ setFontWeight(FontWeight::N800);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"900"))
+ {
+ setFontWeight(FontWeight::N900);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bolder"))
+ {
+ setFontWeight(FontWeight::bolder);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"lighter"))
+ {
+ setFontWeight(FontWeight::lighter);
+ }
+ }
+ break;
+ }
+ case SVGToken::Direction:
+ {
+ break;
+ }
+ case SVGToken::LetterSpacing:
+ {
+ break;
+ }
+ case SVGToken::TextDecoration:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"none"))
+ {
+ setTextDecoration(TextDecoration::none);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"underline"))
+ {
+ setTextDecoration(TextDecoration::underline);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"overline"))
+ {
+ setTextDecoration(TextDecoration::overline);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"line-through"))
+ {
+ setTextDecoration(TextDecoration::line_through);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"blink"))
+ {
+ setTextDecoration(TextDecoration::blink);
+ }
+ }
+ break;
+ }
+ case SVGToken::UnicodeBidi:
+ {
+ break;
+ }
+ case SVGToken::WordSpacing:
+ {
+ break;
+ }
+ case SVGToken::TextAnchor:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"start"))
+ {
+ setTextAnchor(TextAnchor::start);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"middle"))
+ {
+ setTextAnchor(TextAnchor::middle);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"end"))
+ {
+ setTextAnchor(TextAnchor::end);
+ }
+ }
+ break;
+ }
+ case SVGToken::TextAlign:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"left"))
+ {
+ setTextAlign(TextAlign::left);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"right"))
+ {
+ setTextAlign(TextAlign::right);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"center"))
+ {
+ setTextAlign(TextAlign::center);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"justify"))
+ {
+ setTextAlign(TextAlign::justify);
+ }
+ }
+ break;
+ }
+ case SVGToken::Color:
+ {
+ SvgPaint aSvgPaint;
+ OUString aURL;
+ SvgNumber aOpacity;
+
+ if(readSvgPaint(aContent, aSvgPaint, aURL, aOpacity))
+ {
+ maColor = aSvgPaint;
+ if(aOpacity.isSet())
+ {
+ setOpacity(SvgNumber(std::clamp(aOpacity.getNumber(), 0.0, 1.0)));
+ }
+ }
+ break;
+ }
+ case SVGToken::Opacity:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ setOpacity(SvgNumber(std::clamp(aNum.getNumber(), 0.0, 1.0), aNum.getUnit(), aNum.isSet()));
+ }
+ break;
+ }
+ case SVGToken::Visibility:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"visible"))
+ {
+ setVisibility(Visibility::visible);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hidden"))
+ {
+ setVisibility(Visibility::hidden);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"collapse"))
+ {
+ setVisibility(Visibility::collapse);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"inherit"))
+ {
+ setVisibility(Visibility::inherit);
+ }
+ }
+ break;
+ }
+ case SVGToken::Title:
+ {
+ maTitle = aContent;
+ break;
+ }
+ case SVGToken::Desc:
+ {
+ maDesc = aContent;
+ break;
+ }
+ case SVGToken::ClipPathProperty:
+ {
+ readLocalUrl(aContent, maClipPathXLink);
+ break;
+ }
+ case SVGToken::Filter:
+ {
+ readLocalUrl(aContent, maFilterXLink);
+ break;
+ }
+ case SVGToken::Mask:
+ {
+ readLocalUrl(aContent, maMaskXLink);
+ break;
+ }
+ case SVGToken::ClipRule:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrNonzero))
+ {
+ maClipRule = FillRule::nonzero;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrEvenOdd))
+ {
+ maClipRule = FillRule::evenodd;
+ }
+ }
+ break;
+ }
+ case SVGToken::Marker:
+ {
+ // tdf#155819: Using the marker property from a style sheet is equivalent to using all three (start, mid, end).
+ if(mrOwner.getType() == SVGToken::Style)
+ {
+ readLocalUrl(aContent, maMarkerEndXLink);
+ maMarkerStartXLink = maMarkerMidXLink = maMarkerEndXLink;
+ }
+ break;
+ }
+ case SVGToken::MarkerStart:
+ {
+ readLocalUrl(aContent, maMarkerStartXLink);
+ break;
+ }
+ case SVGToken::MarkerMid:
+ {
+ readLocalUrl(aContent, maMarkerMidXLink);
+ break;
+ }
+ case SVGToken::MarkerEnd:
+ {
+ readLocalUrl(aContent, maMarkerEndXLink);
+ break;
+ }
+ case SVGToken::Display:
+ {
+ // There may be display:none statements inside of style defines, e.g. the following line:
+ // style="display:none"
+ // taken from a svg example; this needs to be parsed and set at the owning node. Do not call
+ // mrOwner.parseAttribute(...) here, this would lead to a recursion
+ if(!aContent.isEmpty())
+ {
+ mrOwner.setDisplay(getDisplayFromContent(aContent));
+ }
+ break;
+ }
+ case SVGToken::BaselineShift:
+ {
+ if(!aContent.isEmpty())
+ {
+ SvgNumber aNum;
+
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"sub"))
+ {
+ setBaselineShift(BaselineShift::Sub);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"super"))
+ {
+ setBaselineShift(BaselineShift::Super);
+ }
+ else if(readSingleNumber(aContent, aNum))
+ {
+ maBaselineShiftNumber = aNum;
+
+ if(SvgUnit::percent == aNum.getUnit())
+ {
+ setBaselineShift(BaselineShift::Percentage);
+ }
+ else
+ {
+ setBaselineShift(BaselineShift::Length);
+ }
+ }
+ else
+ {
+ // no BaselineShift or inherit (which is automatically)
+ setBaselineShift(BaselineShift::Baseline);
+ }
+ }
+ break;
+ }
+ case SVGToken::DominantBaseline:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"middle"))
+ {
+ setDominantBaseline(DominantBaseline::Middle);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"hanging"))
+ {
+ setDominantBaseline(DominantBaseline::Hanging);
+ }
+ else
+ {
+ // no DominantBaseline
+ setDominantBaseline(DominantBaseline::Auto);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ // #i125258# ask if fill is a direct hard attribute (no hierarchy)
+ bool SvgStyleAttributes::isFillSet() const
+ {
+ if(mbIsClipPathContent)
+ {
+ return false;
+ }
+ else if(maFill.isSet())
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ const basegfx::BColor* SvgStyleAttributes::getCurrentColor() const
+ {
+ static basegfx::BColor aBlack(0.0, 0.0, 0.0);
+ const basegfx::BColor *aColor = getColor();
+ if( aColor )
+ return aColor;
+ else
+ return &aBlack;
+ }
+
+ const basegfx::BColor* SvgStyleAttributes::getFill() const
+ {
+ if(maFill.isSet())
+ {
+ if(maFill.isCurrent())
+ {
+ return getCurrentColor();
+ }
+ else if(maFill.isOn())
+ {
+ return &maFill.getBColor();
+ }
+ else if(mbIsClipPathContent)
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[0] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[0];
+ const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
+ --maResolvingParent[0];
+
+ return pFill;
+ }
+ }
+ }
+ else if (maNodeFillURL.isEmpty())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[0] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[0];
+ const basegfx::BColor* pFill = pSvgStyleAttributes->getFill();
+ --maResolvingParent[0];
+
+ if(mbIsClipPathContent)
+ {
+ if (pFill)
+ {
+ return pFill;
+ }
+ else
+ {
+ static basegfx::BColor aBlack(0.0, 0.0, 0.0);
+ return &aBlack;
+ }
+ }
+ else
+ {
+ return pFill;
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ const basegfx::BColor* SvgStyleAttributes::getStroke() const
+ {
+ if(maStroke.isSet())
+ {
+ if(maStroke.isCurrent())
+ {
+ return getCurrentColor();
+ }
+ else if(maStroke.isOn())
+ {
+ return &maStroke.getBColor();
+ }
+ }
+ else if (maNodeStrokeURL.isEmpty())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[1] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[1];
+ auto ret = pSvgStyleAttributes->getStroke();
+ --maResolvingParent[1];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ const basegfx::BColor& SvgStyleAttributes::getStopColor() const
+ {
+ if(maStopColor.isCurrent())
+ {
+ return *getCurrentColor();
+ }
+ else
+ {
+ return maStopColor.getBColor();
+ }
+ }
+
+ const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeFill() const
+ {
+ if (!maFill.isSet())
+ {
+ if (!maNodeFillURL.isEmpty())
+ {
+ const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
+
+ if(pNode)
+ {
+ if(SVGToken::LinearGradient == pNode->getType() || SVGToken::RadialGradient == pNode->getType())
+ {
+ return static_cast< const SvgGradientNode* >(pNode);
+ }
+ }
+ }
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[2] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[2];
+ auto ret = pSvgStyleAttributes->getSvgGradientNodeFill();
+ --maResolvingParent[2];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ const SvgGradientNode* SvgStyleAttributes::getSvgGradientNodeStroke() const
+ {
+ if (!maStroke.isSet())
+ {
+ if(!maNodeStrokeURL.isEmpty())
+ {
+ const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
+
+ if(pNode)
+ {
+ if(SVGToken::LinearGradient == pNode->getType() || SVGToken::RadialGradient == pNode->getType())
+ {
+ return static_cast< const SvgGradientNode* >(pNode);
+ }
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[3] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[3];
+ auto ret = pSvgStyleAttributes->getSvgGradientNodeStroke();
+ --maResolvingParent[3];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeFill() const
+ {
+ if (!maFill.isSet())
+ {
+ if (!maNodeFillURL.isEmpty())
+ {
+ const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeFillURL);
+
+ if(pNode)
+ {
+ if(SVGToken::Pattern == pNode->getType())
+ {
+ return static_cast< const SvgPatternNode* >(pNode);
+ }
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[4] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[4];
+ auto ret = pSvgStyleAttributes->getSvgPatternNodeFill();
+ --maResolvingParent[4];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ const SvgPatternNode* SvgStyleAttributes::getSvgPatternNodeStroke() const
+ {
+ if (!maStroke.isSet())
+ {
+ if(!maNodeStrokeURL.isEmpty())
+ {
+ const SvgNode* pNode = mrOwner.getDocument().findSvgNodeById(maNodeStrokeURL);
+
+ if(pNode)
+ {
+ if(SVGToken::Pattern == pNode->getType())
+ {
+ return static_cast< const SvgPatternNode* >(pNode);
+ }
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[5] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[5];
+ auto ret = pSvgStyleAttributes->getSvgPatternNodeStroke();
+ --maResolvingParent[5];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ SvgNumber SvgStyleAttributes::getStrokeWidth() const
+ {
+ if(maStrokeWidth.isSet())
+ {
+ return maStrokeWidth;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[6] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[6];
+ auto ret = pSvgStyleAttributes->getStrokeWidth();
+ --maResolvingParent[6];
+ return ret;
+ }
+
+ if(mbIsClipPathContent)
+ {
+ return SvgNumber(0.0);
+ }
+
+ // default is 1
+ return SvgNumber(1.0);
+ }
+
+ SvgNumber SvgStyleAttributes::getStopOpacity() const
+ {
+ if(maStopOpacity.isSet())
+ {
+ return maStopOpacity;
+ }
+
+ // default is 1
+ return SvgNumber(1.0);
+ }
+
+ SvgNumber SvgStyleAttributes::getFillOpacity() const
+ {
+ if(maFillOpacity.isSet())
+ {
+ return maFillOpacity;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[7] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[7];
+ auto ret = pSvgStyleAttributes->getFillOpacity();
+ --maResolvingParent[7];
+ return ret;
+ }
+
+ // default is 1
+ return SvgNumber(1.0);
+ }
+
+ SvgNumber SvgStyleAttributes::getOpacity() const
+ {
+ if(maOpacity.isSet())
+ {
+ return maOpacity;
+ }
+
+ // This is called from add_postProcess so only check the parent style
+ // if it has a local css style, because it's the first in the stack
+ if(mrOwner.hasLocalCssStyle())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && pSvgStyleAttributes->maOpacity.isSet())
+ {
+ return pSvgStyleAttributes->maOpacity;
+ }
+ }
+
+ // default is 1
+ return SvgNumber(1.0);
+ }
+
+ Visibility SvgStyleAttributes::getVisibility() const
+ {
+ if(Visibility::notset == maVisibility || Visibility::inherit == maVisibility)
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[9] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[9];
+ auto ret = pSvgStyleAttributes->getVisibility();
+ --maResolvingParent[9];
+ return ret;
+ }
+ //default is Visible
+ return Visibility::visible;
+ }
+
+ // Visibility correction/exception for self-exported SVGs:
+ // When Impress exports single or multi-page SVGs, it puts the
+ // single slides into <g visibility="hidden">. Not sure why
+ // this happens, but this leads (correctly) to empty imported
+ // Graphics.
+ // Thus, if Visibility::hidden is active and owner is a SVGToken::G
+ // and it's parent is also a SVGToken::G and it has a Class 'SlideGroup'
+ // set, check if we are an Impress export.
+ // We are an Impress export if an SVG-Node titled 'ooo:meta_slides'
+ // exists.
+ // All together gives:
+ if(Visibility::hidden == maVisibility
+ && SVGToken::G == mrOwner.getType()
+ && nullptr != mrOwner.getDocument().findSvgNodeById("ooo:meta_slides"))
+ {
+ const SvgNode* pParent(mrOwner.getParent());
+
+ if(nullptr != pParent && SVGToken::G == pParent->getType() && pParent->getClass())
+ {
+ const OUString aClass(*pParent->getClass());
+
+ if("SlideGroup" == aClass)
+ {
+ // if we detect this exception,
+ // override Visibility::hidden -> Visibility::visible
+ return Visibility::visible;
+ }
+ }
+ }
+
+ return maVisibility;
+ }
+
+ FillRule SvgStyleAttributes::getFillRule() const
+ {
+ if(FillRule::notset != maFillRule)
+ {
+ return maFillRule;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[10] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[10];
+ auto ret = pSvgStyleAttributes->getFillRule();
+ --maResolvingParent[10];
+ return ret;
+ }
+
+ // default is NonZero
+ return FillRule::nonzero;
+ }
+
+ FillRule SvgStyleAttributes::getClipRule() const
+ {
+ if(FillRule::notset != maClipRule)
+ {
+ return maClipRule;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[25] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[25];
+ auto ret = pSvgStyleAttributes->getClipRule();
+ --maResolvingParent[25];
+ return ret;
+ }
+
+ // default is NonZero
+ return FillRule::nonzero;
+ }
+
+ const SvgNumberVector& SvgStyleAttributes::getStrokeDasharray() const
+ {
+ if(!maStrokeDasharray.empty())
+ {
+ return maStrokeDasharray;
+ }
+ else if(mbStrokeDasharraySet)
+ {
+ // #121221# is set to empty *by purpose*, do not visit parent styles
+ return maStrokeDasharray;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[11] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[11];
+ const SvgNumberVector& ret = pSvgStyleAttributes->getStrokeDasharray();
+ --maResolvingParent[11];
+ return ret;
+ }
+
+ // default empty
+ return maStrokeDasharray;
+ }
+
+ SvgNumber SvgStyleAttributes::getStrokeDashOffset() const
+ {
+ if(maStrokeDashOffset.isSet())
+ {
+ return maStrokeDashOffset;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[12] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[12];
+ auto ret = pSvgStyleAttributes->getStrokeDashOffset();
+ --maResolvingParent[12];
+ return ret;
+ }
+
+ // default is 0
+ return SvgNumber(0.0);
+ }
+
+ StrokeLinecap SvgStyleAttributes::getStrokeLinecap() const
+ {
+ if(maStrokeLinecap != StrokeLinecap::notset)
+ {
+ return maStrokeLinecap;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[13] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[13];
+ auto ret = pSvgStyleAttributes->getStrokeLinecap();
+ --maResolvingParent[13];
+ return ret;
+ }
+
+ // default is StrokeLinecap::butt
+ return StrokeLinecap::butt;
+ }
+
+ StrokeLinejoin SvgStyleAttributes::getStrokeLinejoin() const
+ {
+ if(maStrokeLinejoin != StrokeLinejoin::notset)
+ {
+ return maStrokeLinejoin;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[14] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[14];
+ auto ret = pSvgStyleAttributes->getStrokeLinejoin();
+ --maResolvingParent[14];
+ return ret;
+ }
+
+ // default is StrokeLinejoin::butt
+ return StrokeLinejoin::miter;
+ }
+
+ SvgNumber SvgStyleAttributes::getStrokeMiterLimit() const
+ {
+ if(maStrokeMiterLimit.isSet())
+ {
+ return maStrokeMiterLimit;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[15] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[15];
+ auto ret = pSvgStyleAttributes->getStrokeMiterLimit();
+ --maResolvingParent[15];
+ return ret;
+ }
+
+ // default is 4
+ return SvgNumber(4.0, SvgUnit::none);
+ }
+
+ SvgNumber SvgStyleAttributes::getStrokeOpacity() const
+ {
+ if(maStrokeOpacity.isSet())
+ {
+ return maStrokeOpacity;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[16] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[16];
+ auto ret = pSvgStyleAttributes->getStrokeOpacity();
+ --maResolvingParent[16];
+ return ret;
+ }
+
+ // default is 1
+ return SvgNumber(1.0);
+ }
+
+ const SvgStringVector& SvgStyleAttributes::getFontFamily() const
+ {
+ if(!maFontFamily.empty() && !o3tl::equalsIgnoreAsciiCase(o3tl::trim(maFontFamily[0]), u"inherit"))
+ {
+ return maFontFamily;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[17] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[17];
+ const SvgStringVector& ret = pSvgStyleAttributes->getFontFamily();
+ --maResolvingParent[17];
+ return ret;
+ }
+
+ // default is empty
+ return maFontFamily;
+ }
+
+ SvgNumber SvgStyleAttributes::getFontSizeNumber() const
+ {
+ // default size is 'medium', i.e. 12 pt, or 16px
+ constexpr double aDefaultSize = o3tl::convert(12.0, o3tl::Length::pt, o3tl::Length::px);
+
+ if(maFontSizeNumber.isSet())
+ {
+ if(!maFontSizeNumber.isPositive())
+ return aDefaultSize;
+
+ // #122524# Handle SvgUnit::percent relative to parent FontSize (see SVG1.1
+ // spec 10.10 Font selection properties \91font-size\92, lastline (click 'normative
+ // definition of the property')
+ if(SvgUnit::percent == maFontSizeNumber.getUnit())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if(pSvgStyleAttributes)
+ {
+ const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
+
+ return SvgNumber(
+ aParentNumber.getNumber() * maFontSizeNumber.getNumber() * 0.01,
+ aParentNumber.getUnit(),
+ true);
+ }
+ // if there's no parent style, set the font size based on the default size
+ // 100% = 16px
+ return SvgNumber(
+ maFontSizeNumber.getNumber() * aDefaultSize / 100.0, SvgUnit::px, true);
+ }
+ else if((SvgUnit::em == maFontSizeNumber.getUnit()) || (SvgUnit::ex == maFontSizeNumber.getUnit()))
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if(pSvgStyleAttributes)
+ {
+ const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
+
+ return SvgNumber(
+ aParentNumber.getNumber() * maFontSizeNumber.getNumber(),
+ aParentNumber.getUnit(),
+ true);
+ }
+ }
+
+ return maFontSizeNumber;
+ }
+
+ //In CSS2, the suggested scaling factor between adjacent indexes is 1.2
+ switch(maFontSize)
+ {
+ case FontSize::notset:
+ break;
+ case FontSize::xx_small:
+ {
+ return SvgNumber(aDefaultSize / 1.728);
+ }
+ case FontSize::x_small:
+ {
+ return SvgNumber(aDefaultSize / 1.44);
+ }
+ case FontSize::small:
+ {
+ return SvgNumber(aDefaultSize / 1.2);
+ }
+ case FontSize::smaller:
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+ if(pSvgStyleAttributes)
+ {
+ const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
+ return SvgNumber(aParentNumber.getNumber() / 1.2, aParentNumber.getUnit());
+ }
+ [[fallthrough]];
+ }
+ case FontSize::medium:
+ case FontSize::initial:
+ {
+ return SvgNumber(aDefaultSize);
+ }
+ case FontSize::large:
+ {
+ return SvgNumber(aDefaultSize * 1.2);
+ }
+ case FontSize::larger:
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+ if(pSvgStyleAttributes)
+ {
+ const SvgNumber aParentNumber = pSvgStyleAttributes->getFontSizeNumber();
+ return SvgNumber(aParentNumber.getNumber() * 1.2, aParentNumber.getUnit());
+ }
+ [[fallthrough]];
+ }
+ case FontSize::x_large:
+ {
+ return SvgNumber(aDefaultSize * 1.44);
+ }
+ case FontSize::xx_large:
+ {
+ return SvgNumber(aDefaultSize * 1.728);
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if(pSvgStyleAttributes)
+ {
+ return pSvgStyleAttributes->getFontSizeNumber();
+ }
+
+ return SvgNumber(aDefaultSize);
+ }
+
+ FontStretch SvgStyleAttributes::getFontStretch() const
+ {
+ if(maFontStretch != FontStretch::notset)
+ {
+ if(FontStretch::wider != maFontStretch && FontStretch::narrower != maFontStretch)
+ {
+ return maFontStretch;
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[18] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[18];
+ FontStretch aInherited = pSvgStyleAttributes->getFontStretch();
+ --maResolvingParent[18];
+
+ if(FontStretch::wider == maFontStretch)
+ {
+ aInherited = getWider(aInherited);
+ }
+ else if(FontStretch::narrower == maFontStretch)
+ {
+ aInherited = getNarrower(aInherited);
+ }
+
+ return aInherited;
+ }
+
+ // default is FontStretch::normal
+ return FontStretch::normal;
+ }
+
+ FontStyle SvgStyleAttributes::getFontStyle() const
+ {
+ if(maFontStyle != FontStyle::notset)
+ {
+ return maFontStyle;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[19] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[19];
+ auto ret = pSvgStyleAttributes->getFontStyle();
+ --maResolvingParent[19];
+ return ret;
+ }
+
+ // default is FontStyle::normal
+ return FontStyle::normal;
+ }
+
+ FontWeight SvgStyleAttributes::getFontWeight() const
+ {
+ if(maFontWeight != FontWeight::notset)
+ {
+ if(FontWeight::bolder != maFontWeight && FontWeight::lighter != maFontWeight)
+ {
+ return maFontWeight;
+ }
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[20] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[20];
+ FontWeight aInherited = pSvgStyleAttributes->getFontWeight();
+ --maResolvingParent[20];
+
+ if(FontWeight::bolder == maFontWeight)
+ {
+ aInherited = getBolder(aInherited);
+ }
+ else if(FontWeight::lighter == maFontWeight)
+ {
+ aInherited = getLighter(aInherited);
+ }
+
+ return aInherited;
+ }
+
+ // default is FontWeight::N400 (FontWeight::normal)
+ return FontWeight::N400;
+ }
+
+ TextAlign SvgStyleAttributes::getTextAlign() const
+ {
+ if(maTextAlign != TextAlign::notset)
+ {
+ return maTextAlign;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[21] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[21];
+ auto ret = pSvgStyleAttributes->getTextAlign();
+ --maResolvingParent[21];
+ return ret;
+ }
+
+ // default is TextAlign::left
+ return TextAlign::left;
+ }
+
+ const SvgStyleAttributes* SvgStyleAttributes::getTextDecorationDefiningSvgStyleAttributes() const
+ {
+ if(maTextDecoration != TextDecoration::notset)
+ {
+ return this;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[22] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[22];
+ auto ret = pSvgStyleAttributes->getTextDecorationDefiningSvgStyleAttributes();
+ --maResolvingParent[22];
+ return ret;
+ }
+
+ // default is 0
+ return nullptr;
+ }
+
+ TextDecoration SvgStyleAttributes::getTextDecoration() const
+ {
+ const SvgStyleAttributes* pDefining = getTextDecorationDefiningSvgStyleAttributes();
+
+ if(pDefining)
+ {
+ return pDefining->maTextDecoration;
+ }
+ else
+ {
+ // default is TextDecoration::none
+ return TextDecoration::none;
+ }
+ }
+
+ TextAnchor SvgStyleAttributes::getTextAnchor() const
+ {
+ if(maTextAnchor != TextAnchor::notset)
+ {
+ return maTextAnchor;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[23] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[23];
+ auto ret = pSvgStyleAttributes->getTextAnchor();
+ --maResolvingParent[23];
+ return ret;
+ }
+
+ // default is TextAnchor::start
+ return TextAnchor::start;
+ }
+
+ const basegfx::BColor* SvgStyleAttributes::getColor() const
+ {
+ if(maColor.isSet())
+ {
+ if(maColor.isCurrent())
+ {
+ OSL_ENSURE(false, "Svg error: current color uses current color (!)");
+ return nullptr;
+ }
+ else if(maColor.isOn())
+ {
+ return &maColor.getBColor();
+ }
+ }
+ else
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[24] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[24];
+ auto ret = pSvgStyleAttributes->getColor();
+ --maResolvingParent[24];
+ return ret;
+ }
+ }
+
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getClipPathXLink() const
+ {
+ if(!maClipPathXLink.isEmpty())
+ {
+ return maClipPathXLink;
+ }
+
+ // This is called from add_postProcess so only check the parent style
+ // if it has a local css style, because it's the first in the stack
+ if(mrOwner.hasLocalCssStyle())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes)
+ {
+ return pSvgStyleAttributes->maClipPathXLink;
+ }
+ }
+
+ return OUString();
+ }
+
+ const SvgClipPathNode* SvgStyleAttributes::accessClipPathXLink() const
+ {
+ const OUString aClipPath(getClipPathXLink());
+
+ if(!aClipPath.isEmpty())
+ {
+ return dynamic_cast< const SvgClipPathNode* >(mrOwner.getDocument().findSvgNodeById(aClipPath));
+ }
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getFilterXLink() const
+ {
+ if(!maFilterXLink.isEmpty())
+ {
+ return maFilterXLink;
+ }
+
+ // This is called from add_postProcess so only check the parent style
+ // if it has a local css style, because it's the first in the stack
+ if(mrOwner.hasLocalCssStyle())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes)
+ {
+ return pSvgStyleAttributes->maFilterXLink;
+ }
+ }
+
+ return OUString();
+ }
+
+ const SvgFilterNode* SvgStyleAttributes::accessFilterXLink() const
+ {
+ const OUString aFilter(getFilterXLink());
+
+ if(!aFilter.isEmpty())
+ {
+ return dynamic_cast< const SvgFilterNode* >(mrOwner.getDocument().findSvgNodeById(aFilter));
+ }
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getMaskXLink() const
+ {
+ if(!maMaskXLink.isEmpty())
+ {
+ return maMaskXLink;
+ }
+
+ // This is called from add_postProcess so only check the parent style
+ // if it has a local css style, because it's the first in the stack
+ if(mrOwner.hasLocalCssStyle())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes)
+ {
+ return pSvgStyleAttributes->maMaskXLink;
+ }
+ }
+
+ return OUString();
+ }
+
+ const SvgMaskNode* SvgStyleAttributes::accessMaskXLink() const
+ {
+ const OUString aMask(getMaskXLink());
+
+ if(!aMask.isEmpty())
+ {
+ return dynamic_cast< const SvgMaskNode* >(mrOwner.getDocument().findSvgNodeById(aMask));
+ }
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getMarkerStartXLink() const
+ {
+ if(!maMarkerStartXLink.isEmpty())
+ {
+ return maMarkerStartXLink;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[26] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[26];
+ auto ret = pSvgStyleAttributes->getMarkerStartXLink();
+ --maResolvingParent[26];
+ return ret;
+ }
+
+ return OUString();
+ }
+
+ const SvgMarkerNode* SvgStyleAttributes::accessMarkerStartXLink() const
+ {
+ const OUString aMarker(getMarkerStartXLink());
+
+ if(!aMarker.isEmpty())
+ {
+ return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerStartXLink()));
+ }
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getMarkerMidXLink() const
+ {
+ if(!maMarkerMidXLink.isEmpty())
+ {
+ return maMarkerMidXLink;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[27] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[27];
+ auto ret = pSvgStyleAttributes->getMarkerMidXLink();
+ --maResolvingParent[27];
+ return ret;
+ }
+
+ return OUString();
+ }
+
+ const SvgMarkerNode* SvgStyleAttributes::accessMarkerMidXLink() const
+ {
+ const OUString aMarker(getMarkerMidXLink());
+
+ if(!aMarker.isEmpty())
+ {
+ return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerMidXLink()));
+ }
+ return nullptr;
+ }
+
+ OUString SvgStyleAttributes::getMarkerEndXLink() const
+ {
+ if(!maMarkerEndXLink.isEmpty())
+ {
+ return maMarkerEndXLink;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[28] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[28];
+ auto ret = pSvgStyleAttributes->getMarkerEndXLink();
+ --maResolvingParent[28];
+ return ret;
+ }
+
+ return OUString();
+ }
+
+ const SvgMarkerNode* SvgStyleAttributes::accessMarkerEndXLink() const
+ {
+ const OUString aMarker(getMarkerEndXLink());
+
+ if(!aMarker.isEmpty())
+ {
+ return dynamic_cast< const SvgMarkerNode* >(mrOwner.getDocument().findSvgNodeById(getMarkerEndXLink()));
+ }
+ return nullptr;
+ }
+
+ SvgNumber SvgStyleAttributes::getBaselineShiftNumber() const
+ {
+ // #122524# Handle SvgUnit::percent relative to parent BaselineShift
+ if(SvgUnit::percent == maBaselineShiftNumber.getUnit())
+ {
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[8] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[8];
+ const SvgNumber aParentNumber = pSvgStyleAttributes->getBaselineShiftNumber();
+ --maResolvingParent[8];
+
+ return SvgNumber(
+ aParentNumber.getNumber() * maBaselineShiftNumber.getNumber() * 0.01,
+ aParentNumber.getUnit(),
+ true);
+ }
+ }
+
+ return maBaselineShiftNumber;
+ }
+
+ BaselineShift SvgStyleAttributes::getBaselineShift() const
+ {
+ if(maBaselineShift != BaselineShift::Baseline)
+ {
+ return maBaselineShift;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[29] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[29];
+ auto ret = pSvgStyleAttributes->getBaselineShift();
+ --maResolvingParent[29];
+ return ret;
+ }
+
+ return BaselineShift::Baseline;
+ }
+
+ DominantBaseline SvgStyleAttributes::getDominantBaseline() const
+ {
+ if(maDominantBaseline != DominantBaseline::Auto)
+ {
+ return maDominantBaseline;
+ }
+
+ const SvgStyleAttributes* pSvgStyleAttributes = getParentStyle();
+
+ if (pSvgStyleAttributes && maResolvingParent[30] < nStyleDepthLimit)
+ {
+ ++maResolvingParent[30];
+ auto ret = pSvgStyleAttributes->getDominantBaseline();
+ --maResolvingParent[30];
+ return ret;
+ }
+
+ return DominantBaseline::Auto;
+ }
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/svgio/source/svgreader/svgstylenode.cxx b/svgio/source/svgreader/svgstylenode.cxx
new file mode 100644
index 0000000000..88b380cf3b
--- /dev/null
+++ b/svgio/source/svgreader/svgstylenode.cxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgstylenode.hxx>
+#include <svgdocument.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+namespace svgio::svgreader
+{
+ SvgStyleNode::SvgStyleNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Style, rDocument, pParent),
+ mbTextCss(true)
+ {
+ }
+
+ // #i125258# no parent when we are a CssStyle holder to break potential loops because
+ // when using CssStyles we jump uncontrolled inside the node tree hierarchy
+ bool SvgStyleNode::supportsParentStyle() const
+ {
+ if(isTextCss())
+ {
+ return false;
+ }
+
+ // call parent
+ return SvgNode::supportsParentStyle();
+ }
+
+ void SvgStyleNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Type:
+ {
+ if(!aContent.isEmpty())
+ {
+ if(!o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"text/css"))
+ {
+ setTextCss(false);
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgStyleNode::addCssStyleSheet(std::u16string_view aSelectors, const SvgStyleAttributes& rNewStyle)
+ {
+ // aSelectors: CssStyle selectors, any combination, no comma separations, no spaces at start/end
+ // rNewStyle: the already prepared style to register on that name
+ if(aSelectors.empty())
+ return;
+
+ std::vector< OUString > aSelectorParts;
+ const sal_Int32 nLen(aSelectors.size());
+ sal_Int32 nPos(0);
+ OUStringBuffer aToken;
+
+ // split into single tokens (currently only space separator)
+ while(nPos < nLen)
+ {
+ const sal_Int32 nInitPos(nPos);
+ copyToLimiter(aSelectors, u' ', nPos, aToken, nLen);
+ skip_char(aSelectors, u' ', nPos, nLen);
+ const OUString aSelectorPart(o3tl::trim(aToken));
+ aToken.setLength(0);
+
+ if(!aSelectorPart.isEmpty())
+ {
+ aSelectorParts.push_back(aSelectorPart);
+ }
+
+ if(nInitPos == nPos)
+ {
+ OSL_ENSURE(false, "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+
+ if(aSelectorParts.empty())
+ return;
+
+ OUStringBuffer aConcatenatedSelector;
+
+ // re-combine without spaces, create a unique name (for now)
+ for(const auto &a : aSelectorParts)
+ {
+ aConcatenatedSelector.append(a);
+ }
+
+ // CssStyles in SVG are currently not completely supported; the current idea for
+ // supporting the needed minimal set is to register CssStyles associated to a string
+ // which is just the space-char cleaned, concatenated Selectors. The part to 'match'
+ // these is in fillCssStyleVectorUsingHierarchyAndSelectors. There, the same string is
+ // built up using the priorities of local CssStyle, Id, Class and other info combined
+ // with the existing hierarchy. This creates a specificity and priority-sorted local
+ // list for each node which is then chained using get/setCssStyleParent.
+ // The current solution is capable of solving space-separated selectors which can be
+ // mixed between Id, Class and type specifiers.
+ // When CssStyles need more specific solving, the start point is here; remember the
+ // needed infos not in maIdStyleTokenMapperList at the document, but select evtl.
+ // more specific infos there in a class capable of handling more complex matchings.
+ // Additionally fillCssStyleVector (or the mechanism above that when a linked list of
+ // SvgStyleAttributes will not do it) will have to be adapted to make use of it.
+
+ // register new style at document for (evtl. concatenated) stylename
+ const_cast< SvgDocument& >(getDocument()).addSvgStyleAttributesToMapper(aConcatenatedSelector.makeStringAndClear(), rNewStyle);
+ }
+
+ void SvgStyleNode::addCssStyleSheet(std::u16string_view aSelectors, std::u16string_view aContent)
+ {
+ // aSelectors: possible comma-separated list of CssStyle definitions, no spaces at start/end
+ // aContent: the svg style definitions as string
+ if(aSelectors.empty() || aContent.empty())
+ return;
+
+ // comma-separated split (Css abbreviation for same style for multiple selectors)
+ const sal_Int32 nLen(aSelectors.size());
+ sal_Int32 nPos(0);
+ OUStringBuffer aToken;
+
+ while(nPos < nLen)
+ {
+ const sal_Int32 nInitPos(nPos);
+ copyToLimiter(aSelectors, u',', nPos, aToken, nLen);
+ skip_char(aSelectors, u' ', u',', nPos, nLen);
+
+ const OUString aSingleName(o3tl::trim(aToken));
+ aToken.setLength(0);
+
+ // add the current css class only if wasn't previously added
+ auto [aIterator, bIsNew] = maSvgStyleAttributes.try_emplace(aSingleName);
+ if (bIsNew)
+ {
+ // create new style and add to local list (for ownership control) and
+ // in case it's written to again in future classes to prevent overwrites
+ aIterator->second = std::make_unique<SvgStyleAttributes>(*this);
+ }
+ const std::unique_ptr<SvgStyleAttributes>& pCurrentStyle = aIterator->second;
+
+ // fill with content
+ pCurrentStyle->readCssStyle(aContent);
+
+ if(aSingleName.getLength())
+ {
+ addCssStyleSheet(aSingleName, *pCurrentStyle);
+ }
+
+ if(nInitPos == nPos)
+ {
+ OSL_ENSURE(false, "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+ }
+
+ void SvgStyleNode::addCssStyleSheet(std::u16string_view aSelectorsAndContent)
+ {
+ const sal_Int32 nLen(aSelectorsAndContent.size());
+
+ if(!nLen)
+ return;
+
+ sal_Int32 nPos(0);
+ OUStringBuffer aToken;
+
+ while(nPos < nLen)
+ {
+ // read the full selectors (may be multiple, comma-separated)
+ const sal_Int32 nInitPos(nPos);
+ skip_char(aSelectorsAndContent, u' ', nPos, nLen);
+ copyToLimiter(aSelectorsAndContent, u'{', nPos, aToken, nLen);
+ skip_char(aSelectorsAndContent, u' ', u'{', nPos, nLen);
+
+ const OUString aSelectors(o3tl::trim(aToken));
+ aToken.setLength(0);
+ OUString aContent;
+
+ if(!aSelectors.isEmpty() && nPos < nLen)
+ {
+ // isolate content as text, embraced by '{' and '}'
+ copyToLimiter(aSelectorsAndContent, u'}', nPos, aToken, nLen);
+ skip_char(aSelectorsAndContent, u' ', u'}', nPos, nLen);
+
+ aContent = o3tl::trim(aToken);
+ aToken.setLength(0);
+ }
+
+ if(!aSelectors.isEmpty() && !aContent.isEmpty())
+ {
+ addCssStyleSheet(aSelectors, aContent);
+ }
+
+ if(nInitPos == nPos)
+ {
+ OSL_ENSURE(false, "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgsvgnode.cxx b/svgio/source/svgreader/svgsvgnode.cxx
new file mode 100644
index 0000000000..09c0810698
--- /dev/null
+++ b/svgio/source/svgreader/svgsvgnode.cxx
@@ -0,0 +1,826 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgsvgnode.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <svgdocument.hxx>
+
+namespace svgio::svgreader
+{
+ SvgSvgNode::SvgSvgNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Svg, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ mbStyleAttributesInitialized(false) // #i125258#
+ {
+ }
+
+ // #i125258#
+ void SvgSvgNode::initializeStyleAttributes()
+ {
+ if(mbStyleAttributesInitialized)
+ return;
+
+ // #i125258# determine if initial values need to be initialized with hard values
+ // for the case that this is the outmost SVG statement and it has no parent
+ // stale (CssStyle for svg may be defined)
+ bool bSetInitialValues(true);
+
+ if(getParent())
+ {
+ // #i125258# no initial values when it's a SVG element embedded in SVG
+ bSetInitialValues = false;
+ }
+
+ if(bSetInitialValues)
+ {
+ const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
+
+ if(pStyles && pStyles->getParentStyle())
+ {
+ // SVG has a parent style (probably CssStyle), check if fill is set there anywhere
+ // already. If yes, do not set the default fill (black)
+ bool bFillSet(false);
+ const SvgStyleAttributes* pParentStyle = pStyles->getParentStyle();
+
+ while(pParentStyle && !bFillSet)
+ {
+ bFillSet = pParentStyle->isFillSet();
+ pParentStyle = pParentStyle->getParentStyle();
+ }
+
+ if(bFillSet)
+ {
+ // #125258# no initial values when SVG has a parent style at which a fill
+ // is already set
+ bSetInitialValues = false;
+ }
+ }
+ }
+
+ if(bSetInitialValues)
+ {
+ // #i125258# only set if not yet initialized (SvgSvgNode::parseAttribute is already done,
+ // just setting may revert an already set valid value)
+ if(!maSvgStyleAttributes.isFillSet())
+ {
+ // #i125258# initial fill is black (see SVG1.1 spec)
+ maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true));
+ }
+ }
+
+ mbStyleAttributesInitialized = true;
+ }
+
+ SvgSvgNode::~SvgSvgNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const
+ {
+ // #i125258# svg node can have CssStyles, too, so check for it here
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgSvgNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::ViewBox:
+ {
+ const basegfx::B2DRange aRange(readViewBox(aContent, *this));
+
+ if(!aRange.isEmpty())
+ {
+ setViewBox(&aRange);
+ }
+ break;
+ }
+ case SVGToken::PreserveAspectRatio:
+ {
+ maSvgAspectRatio = readSvgAspectRatio(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Version:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maVersion = aNum;
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgSvgNode::seekReferenceWidth(double& fWidth, bool& bHasFound) const
+ {
+ if (!getParent() || bHasFound)
+ {
+ return;
+ }
+ const SvgSvgNode* pParentSvgSvgNode = nullptr;
+ // enclosing svg might have relative width, need to cumulate them till they are
+ // resolved somewhere up in the node tree
+ double fPercentage(1.0);
+ for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent())
+ {
+ // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition
+ pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent);
+ if (pParentSvgSvgNode)
+ {
+ if (pParentSvgSvgNode->getViewBox())
+ {
+ // viewbox values are already in 'user unit'.
+ fWidth = pParentSvgSvgNode->getViewBox()->getWidth() * fPercentage;
+ bHasFound = true;
+ }
+ else
+ {
+ // take absolute value or cumulate percentage
+ if (pParentSvgSvgNode->getWidth().isSet())
+ {
+ if (SvgUnit::percent == pParentSvgSvgNode->getWidth().getUnit())
+ {
+ fPercentage *= pParentSvgSvgNode->getWidth().getNumber() * 0.01;
+ }
+ else
+ {
+ fWidth = pParentSvgSvgNode->getWidth().solveNonPercentage(*pParentSvgSvgNode) * fPercentage;
+ bHasFound = true;
+ }
+ } // not set => width=100% => factor 1, no need for else
+ }
+ }
+ }
+ }
+
+ void SvgSvgNode::seekReferenceHeight(double& fHeight, bool& bHasFound) const
+ {
+ if (!getParent() || bHasFound)
+ {
+ return;
+ }
+ const SvgSvgNode* pParentSvgSvgNode = nullptr;
+ // enclosing svg might have relative width and height, need to cumulate them till they are
+ // resolved somewhere up in the node tree
+ double fPercentage(1.0);
+ for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent())
+ {
+ // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition
+ pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent);
+ if (pParentSvgSvgNode)
+ {
+ if (pParentSvgSvgNode->getViewBox())
+ {
+ // viewbox values are already in 'user unit'.
+ fHeight = pParentSvgSvgNode->getViewBox()->getHeight() * fPercentage;
+ bHasFound = true;
+ }
+ else
+ {
+ // take absolute value or cumulate percentage
+ if (pParentSvgSvgNode->getHeight().isSet())
+ {
+ if (SvgUnit::percent == pParentSvgSvgNode->getHeight().getUnit())
+ {
+ fPercentage *= pParentSvgSvgNode->getHeight().getNumber() * 0.01;
+ }
+ else
+ {
+ fHeight = pParentSvgSvgNode->getHeight().solveNonPercentage(*pParentSvgSvgNode) * fPercentage;
+ bHasFound = true;
+ }
+ } // not set => height=100% => factor 1, no need for else
+ }
+ }
+ }
+ }
+
+// ToDo: Consider attribute overflow in method decomposeSvgNode
+ void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aSequence;
+
+ // #i125258# check now if we need to init some style settings locally. Do not do this
+ // in the constructor, there is not yet information e.g. about existing CssStyles.
+ // Here all nodes are read and interpreted
+ const_cast< SvgSvgNode* >(this)->initializeStyleAttributes();
+
+ // decompose children
+ SvgNode::decomposeSvgNode(aSequence, bReferenced);
+
+ if(!aSequence.empty())
+ {
+ if(getParent())
+ {
+ // #i122594# if width/height is not given, it's 100% (see 5.1.2 The 'svg' element in SVG1.1 spec).
+ // If it is relative, the question is to what. The previous implementation assumed relative to the
+ // local ViewBox which is implied by (4.2 Basic data types):
+
+ // "Note that the non-property <length> definition also allows a percentage unit identifier.
+ // The meaning of a percentage length value depends on the attribute for which the percentage
+ // length value has been specified. Two common cases are: (a) when a percentage length value
+ // represents a percentage of the viewport width or height (refer to the section that discusses
+ // units in general), and (b) when a percentage length value represents a percentage of the
+ // bounding box width or height on a given object (refer to the section that describes object
+ // bounding box units)."
+
+ // Comparisons with common browsers show that it's mostly interpreted relative to the viewport
+ // of the parent, and so does the new implementation.
+
+ // Extract known viewport data
+ // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values
+
+ // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2
+ // value 0.0 here is only to initialize variable
+ bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
+ double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0);
+
+ bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
+ double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0);
+
+ // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2
+ bool bXIsAbsolute((getX().isSet() && SvgUnit::percent != getX().getUnit()) || !getX().isSet());
+ double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0);
+
+ bool bYIsAbsolute((getY().isSet() && SvgUnit::percent != getY().getUnit()) || !getY().isSet());
+ double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0);
+
+ if ( !bXIsAbsolute || !bWidthIsAbsolute)
+ {
+ // get width of enclosing svg and resolve percentage in x and width;
+ double fWReference(0.0);
+ bool bHasFoundWidth(false);
+ seekReferenceWidth(fWReference, bHasFoundWidth);
+ if (!bHasFoundWidth)
+ {
+ if (getViewBox())
+ {
+ fWReference = getViewBox()->getWidth();
+ }
+ else
+ {
+ // Even outermost svg has not all information to resolve relative values,
+ // I use content itself as fallback to set missing values for viewport
+ // Any better idea for such ill structured svg documents?
+ const basegfx::B2DRange aChildRange(
+ aSequence.getB2DRange(
+ drawinglayer::geometry::ViewInformation2D()));
+ fWReference = aChildRange.getWidth();
+ }
+ }
+ // referenced values are already in 'user unit'
+ if (!bXIsAbsolute)
+ {
+ fX = getX().getNumber() * 0.01 * fWReference;
+ }
+ if (!bWidthIsAbsolute)
+ {
+ fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference;
+ }
+ }
+
+ if ( !bYIsAbsolute || !bHeightIsAbsolute)
+ {
+ // get height of enclosing svg and resolve percentage in y and height
+ double fHReference(0.0);
+ bool bHasFoundHeight(false);
+ seekReferenceHeight(fHReference, bHasFoundHeight);
+ if (!bHasFoundHeight)
+ {
+ if (getViewBox())
+ {
+ fHReference = getViewBox()->getHeight();
+ }
+ else
+ {
+ // Even outermost svg has not all information to resolve relative values,
+ // I use content itself as fallback to set missing values for viewport
+ // Any better idea for such ill structured svg documents?
+ const basegfx::B2DRange aChildRange(
+ aSequence.getB2DRange(
+ drawinglayer::geometry::ViewInformation2D()));
+ fHReference = aChildRange.getHeight();
+ }
+ }
+
+ // referenced values are already in 'user unit'
+ if (!bYIsAbsolute)
+ {
+ fY = getY().getNumber() * 0.01 * fHReference;
+ }
+ if (!bHeightIsAbsolute)
+ {
+ fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference;
+ }
+ }
+
+ if(getViewBox())
+ {
+ // SVG 1.1 defines in section 7.7 that a negative value for width or height
+ // in viewBox is an error and that 0.0 disables rendering
+ if(basegfx::fTools::more(getViewBox()->getWidth(),0.0) && basegfx::fTools::more(getViewBox()->getHeight(),0.0))
+ {
+ // create target range homing x,y, width and height as calculated above
+ const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH);
+
+ if(aTarget.equal(*getViewBox()))
+ {
+ // no mapping needed, append
+ rTarget.append(aSequence);
+ }
+ else
+ {
+ // create mapping
+ // #i122610 SVG 1.1 defines in section 5.1.2 that if the attribute preserveAspectRatio is not specified,
+ // then the effect is as if a value of 'xMidYMid meet' were specified.
+ SvgAspectRatio aRatioDefault(SvgAlign::xMidYMid,true);
+ const SvgAspectRatio& rRatio = getSvgAspectRatio().isSet()? getSvgAspectRatio() : aRatioDefault;
+
+ // let mapping be created from SvgAspectRatio
+ const basegfx::B2DHomMatrix aEmbeddingTransform(
+ rRatio.createMapping(aTarget, *getViewBox()));
+
+ // prepare embedding in transformation
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aEmbeddingTransform,
+ drawinglayer::primitive2d::Primitive2DContainer(aSequence)));
+
+ if(rRatio.isMeetOrSlice())
+ {
+ // embed in transformation
+ rTarget.push_back(xRef);
+ }
+ else
+ {
+ // need to embed in MaskPrimitive2D, too
+ const drawinglayer::primitive2d::Primitive2DReference xMask(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aTarget)),
+ drawinglayer::primitive2d::Primitive2DContainer { xRef }));
+
+ rTarget.push_back(xMask);
+ }
+ }
+ }
+ }
+ else // no viewBox attribute
+ {
+ // Svg defines that a negative value is an error and that 0.0 disables rendering
+ if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0))
+ {
+ if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
+ {
+ // embed in transform
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ basegfx::utils::createTranslateB2DHomMatrix(fX, fY),
+ std::move(aSequence)));
+
+ aSequence = drawinglayer::primitive2d::Primitive2DContainer { xRef, };
+ }
+
+ // embed in MaskPrimitive2D to clip
+ const drawinglayer::primitive2d::Primitive2DReference xMask(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRange(fX, fY, fX + fW, fY + fH))),
+ drawinglayer::primitive2d::Primitive2DContainer(aSequence)));
+
+ // append
+ rTarget.push_back(xMask);
+ }
+ }
+ }
+ else // Outermost SVG element
+ {
+ // Svg defines that a negative value is an error and that 0.0 disables rendering
+ // isPositive() not usable because it allows 0.0 in contrast to mathematical definition of 'positive'
+ const bool bWidthInvalid(getWidth().isSet() && basegfx::fTools::lessOrEqual(getWidth().getNumber(), 0.0));
+ const bool bHeightInvalid(getHeight().isSet() && basegfx::fTools::lessOrEqual(getHeight().getNumber(), 0.0));
+ if(!bWidthInvalid && !bHeightInvalid)
+ {
+ basegfx::B2DRange aSvgCanvasRange; // viewport
+ double fW = 0.0; // dummy values
+ double fH = 0.0;
+ if (const basegfx::B2DRange* pBox = getViewBox())
+ {
+ // SVG 1.1 defines in section 7.7 that a negative value for width or height
+ // in viewBox is an error and that 0.0 disables rendering
+ const double fViewBoxWidth = pBox->getWidth();
+ const double fViewBoxHeight = pBox->getHeight();
+ if(basegfx::fTools::more(fViewBoxWidth,0.0) && basegfx::fTools::more(fViewBoxHeight,0.0))
+ {
+ // The intrinsic aspect ratio of the svg element is given by absolute values of svg width and svg height
+ // or by the width and height of the viewBox, if svg width or svg height is relative.
+ // see SVG 1.1 section 7.12
+ bool bNeedsMapping(true);
+ const bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
+ const bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
+ const double fViewBoxRatio(fViewBoxWidth/fViewBoxHeight);
+ if(bWidthIsAbsolute && bHeightIsAbsolute)
+ {
+ fW = getWidth().solveNonPercentage(*this);
+ fH = getHeight().solveNonPercentage(*this);
+ aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
+ }
+ else if (bWidthIsAbsolute)
+ {
+ fW = getWidth().solveNonPercentage(*this);
+ fH = fW / fViewBoxRatio ;
+ aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
+ }
+ else if (bHeightIsAbsolute)
+ {
+ fH = getHeight().solveNonPercentage(*this);
+ fW = fH * fViewBoxRatio ;
+ aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
+ }
+ else
+ {
+ // There exists no parent to resolve relative width or height.
+ // Use child size as fallback and expand to aspect ratio given
+ // by the viewBox. No mapping.
+ // We get viewport >= content, therefore no clipping.
+ bNeedsMapping = false;
+
+ const double fChildWidth(pBox->getWidth());
+ const double fChildHeight(pBox->getHeight());
+ const double fLeft(pBox->getMinX());
+ const double fTop(pBox->getMinY());
+ if ( fChildWidth / fViewBoxWidth > fChildHeight / fViewBoxHeight )
+ { // expand y
+ fW = fChildWidth;
+ fH = fChildWidth / fViewBoxRatio;
+ }
+ else
+ { // expand x
+ fH = fChildHeight;
+ fW = fChildHeight * fViewBoxRatio;
+ }
+ aSvgCanvasRange = basegfx::B2DRange(fLeft, fTop, fLeft + fW, fTop + fH);
+ }
+
+ if (bNeedsMapping)
+ {
+ // create mapping
+ // SVG 1.1 defines in section 5.1.2 that if the attribute preserveAspectRatio is not specified,
+ // then the effect is as if a value of 'xMidYMid meet' were specified.
+ SvgAspectRatio aRatioDefault(SvgAlign::xMidYMid, true);
+ const SvgAspectRatio& rRatio = getSvgAspectRatio().isSet()? getSvgAspectRatio() : aRatioDefault;
+
+ basegfx::B2DHomMatrix aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *pBox);
+ // no need to check ratio here for slice, the outermost Svg will
+ // be clipped anyways (see below)
+
+ // scale content to viewBox definitions
+ const drawinglayer::primitive2d::Primitive2DReference xTransform(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aViewBoxMapping,
+ std::move(aSequence)));
+
+ aSequence = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
+ }
+ }
+ }
+ else // no viewbox => no mapping
+ {
+ const bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
+ const bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
+ if (bWidthIsAbsolute && bHeightIsAbsolute)
+ {
+ fW =getWidth().solveNonPercentage(*this);
+ fH =getHeight().solveNonPercentage(*this);
+ aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
+ }
+ else
+ {
+ // There exists no parent to resolve relative width or height.
+ // Use child size as fallback. We get viewport >= content, therefore no clipping.
+ const basegfx::B2DRange aChildRange(
+ aSequence.getB2DRange(
+ drawinglayer::geometry::ViewInformation2D()));
+ const double fChildWidth(aChildRange.getWidth());
+ const double fChildHeight(aChildRange.getHeight());
+ const double fChildLeft(aChildRange.getMinX());
+ const double fChildTop(aChildRange.getMinY());
+ fW = bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : fChildWidth;
+ fH = bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : fChildHeight;
+ const double fLeft(bWidthIsAbsolute ? 0.0 : fChildLeft);
+ const double fTop(bHeightIsAbsolute ? 0.0 : fChildTop);
+ aSvgCanvasRange = basegfx::B2DRange(fLeft, fTop, fLeft+fW, fTop+fH);
+ }
+
+ }
+
+ // to be completely correct in Svg sense it is necessary to clip
+ // the whole content to the given canvas. I choose here to do this
+ // initially despite I found various examples of Svg files out there
+ // which have no correct values for this clipping. It's correct
+ // due to the Svg spec.
+
+ // different from Svg we have the possibility with primitives to get
+ // a correct bounding box for the geometry. Get it for evtl. taking action
+ const basegfx::B2DRange aContentRange(
+ aSequence.getB2DRange(
+ drawinglayer::geometry::ViewInformation2D()));
+
+ if(aSvgCanvasRange.isInside(aContentRange))
+ {
+ // no clip needed, but an invisible HiddenGeometryPrimitive2D
+ // to allow getting the full Svg range using the primitive mechanisms.
+ // This is needed since e.g. an SdrObject using this as graphic will
+ // create a mapping transformation to exactly map the content to its
+ // real life size
+ const drawinglayer::primitive2d::Primitive2DReference xLine(
+ new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
+ basegfx::utils::createPolygonFromRect(
+ aSvgCanvasRange),
+ basegfx::BColor(0.0, 0.0, 0.0)));
+ const drawinglayer::primitive2d::Primitive2DReference xHidden(
+ new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer { xLine }));
+
+ aSequence.push_back(xHidden);
+ }
+ else if(aSvgCanvasRange.overlaps(aContentRange))
+ {
+ // Clip is necessary. This will make Svg images evtl. smaller
+ // than wanted from Svg (the free space which may be around it is
+ // conform to the Svg spec), but avoids an expensive and unnecessary
+ // clip. Keep the full Svg range here to get the correct mappings
+ // to objects using this. Optimizations can be done in the processors
+ const drawinglayer::primitive2d::Primitive2DReference xMask(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ aSvgCanvasRange)),
+ std::move(aSequence)));
+
+ aSequence = drawinglayer::primitive2d::Primitive2DContainer { xMask };
+ }
+ else
+ {
+ // not inside, no overlap. Empty Svg
+ aSequence.clear();
+ }
+
+ if(!aSequence.empty())
+ {
+ // Another correction:
+ // If no Width/Height is set (usually done in
+ // <svg ... width="215.9mm" height="279.4mm" >) which
+ // is the case for own-Impress-exports, assume that
+ // the Units are already 100ThMM.
+ // Maybe only for own-Impress-exports, thus may need to be
+ // &&ed with getDocument().findSvgNodeById("ooo:meta_slides"),
+ // but does not need to be.
+ bool bEmbedInFinalTransformPxTo100ThMM(true);
+
+ if(getDocument().findSvgNodeById("ooo:meta_slides")
+ && !getWidth().isSet()
+ && !getHeight().isSet())
+ {
+ bEmbedInFinalTransformPxTo100ThMM = false;
+ }
+
+ if(bEmbedInFinalTransformPxTo100ThMM)
+ {
+ // embed in transform primitive to scale to 1/100th mm
+ // to get from Svg coordinates (px) to drawinglayer coordinates
+ constexpr double fScaleTo100thmm(o3tl::convert(1.0, o3tl::Length::px, o3tl::Length::mm100));
+ const basegfx::B2DHomMatrix aTransform(
+ basegfx::utils::createScaleB2DHomMatrix(
+ fScaleTo100thmm,
+ fScaleTo100thmm));
+
+ const drawinglayer::primitive2d::Primitive2DReference xTransform(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ aTransform,
+ std::move(aSequence)));
+
+ aSequence = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
+ }
+
+ // append to result
+ rTarget.append(aSequence);
+ }
+ }
+ }
+ }
+
+ if(!(aSequence.empty() && !getParent() && getViewBox()))
+ return;
+
+ // tdf#118232 No geometry, Outermost SVG element and we have a ViewBox.
+ // Create a HiddenGeometry Primitive containing an expanded
+ // hairline geometry to have the size contained
+ const drawinglayer::primitive2d::Primitive2DReference xLine(
+ new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
+ basegfx::utils::createPolygonFromRect(
+ *getViewBox()),
+ basegfx::BColor(0.0, 0.0, 0.0)));
+ const drawinglayer::primitive2d::Primitive2DReference xHidden(
+ new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer { xLine }));
+
+ rTarget.push_back(xHidden);
+ }
+
+ basegfx::B2DRange SvgSvgNode::getCurrentViewPort() const
+ {
+ if(getViewBox())
+ {
+ return *(getViewBox());
+ }
+ else // viewport should be given by x, y, width, and height
+ {
+ // Extract known viewport data
+ // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values
+ if (getParent())
+ {
+ // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2
+ // value 0.0 here is only to initialize variable
+ bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
+ double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0);
+ bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
+ double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0);
+
+ // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2
+ bool bXIsAbsolute((getX().isSet() && SvgUnit::percent != getX().getUnit()) || !getX().isSet());
+ double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0);
+
+ bool bYIsAbsolute((getY().isSet() && SvgUnit::percent != getY().getUnit()) || !getY().isSet());
+ double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0);
+
+ if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute)
+ {
+ return basegfx::B2DRange(fX, fY, fX+fW, fY+fH);
+ }
+ else // try to resolve relative values
+ {
+ if (!bXIsAbsolute || !bWidthIsAbsolute)
+ {
+ // get width of enclosing svg and resolve percentage in x and width
+ double fWReference(0.0);
+ bool bHasFoundWidth(false);
+ seekReferenceWidth(fWReference, bHasFoundWidth);
+ // referenced values are already in 'user unit'
+ if (!bXIsAbsolute && bHasFoundWidth)
+ {
+ fX = getX().getNumber() * 0.01 * fWReference;
+ bXIsAbsolute = true;
+ }
+ if (!bWidthIsAbsolute && bHasFoundWidth)
+ {
+ fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference;
+ bWidthIsAbsolute = true;
+ }
+ }
+ if (!bYIsAbsolute || !bHeightIsAbsolute)
+ {
+ // get height of enclosing svg and resolve percentage in y and height
+ double fHReference(0.0);
+ bool bHasFoundHeight(false);
+ seekReferenceHeight(fHReference, bHasFoundHeight);
+ // referenced values are already in 'user unit'
+ if (!bYIsAbsolute && bHasFoundHeight)
+ {
+ fY = getY().getNumber() * 0.01 * fHReference;
+ bYIsAbsolute = true;
+ }
+ if (!bHeightIsAbsolute && bHasFoundHeight)
+ {
+ fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference;
+ bHeightIsAbsolute = true;
+ }
+ }
+
+ if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute)
+ {
+ return basegfx::B2DRange(fX, fY, fX+fW, fY+fH);
+ }
+ else // relative values could not be resolved, there exists no fallback
+ {
+ return SvgNode::getCurrentViewPort();
+ }
+ }
+ }
+ else //outermost svg
+ {
+ // If width or height is not provided, the default would be 100%, see SVG 1.1 section 5.1.2
+ // But here it cannot be resolved and no fallback exists.
+ // SVG 1.1 defines in section 5.1.2 that x,y has no meaning for the outermost SVG element.
+ bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
+ bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
+ if (bWidthIsAbsolute && bHeightIsAbsolute)
+ {
+ double fW( getWidth().solveNonPercentage(*this) );
+ double fH( getHeight().solveNonPercentage(*this) );
+ return basegfx::B2DRange(0.0, 0.0, fW, fH);
+ }
+ else // no fallback exists
+ {
+ return SvgNode::getCurrentViewPort();
+ }
+ }
+// TODO: Is it possible to decompose and use the bounding box of the children, if even the
+// outermost svg has no information to resolve percentage? Is it worth, how expensive is it?
+
+ }
+ }
+
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgsymbolnode.cxx b/svgio/source/svgreader/svgsymbolnode.cxx
new file mode 100644
index 0000000000..6e18d4bca0
--- /dev/null
+++ b/svgio/source/svgreader/svgsymbolnode.cxx
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgsymbolnode.hxx>
+
+namespace svgio::svgreader
+{
+ SvgSymbolNode::SvgSymbolNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Symbol, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgSymbolNode::~SvgSymbolNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgSymbolNode::getSvgStyleAttributes() const
+ {
+ return &maSvgStyleAttributes;
+ }
+
+ void SvgSymbolNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::ViewBox:
+ {
+ readViewBox(aContent, *this);
+ break;
+ }
+ case SVGToken::PreserveAspectRatio:
+ {
+ maSvgAspectRatio = readSvgAspectRatio(aContent);
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtextnode.cxx b/svgio/source/svgreader/svgtextnode.cxx
new file mode 100644
index 0000000000..ca17c2fc42
--- /dev/null
+++ b/svgio/source/svgreader/svgtextnode.cxx
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtextnode.hxx>
+#include <svgcharacternode.hxx>
+#include <svgstyleattributes.hxx>
+#include <svgtrefnode.hxx>
+#include <svgtextpathnode.hxx>
+#include <svgtspannode.hxx>
+#include <osl/diagnose.h>
+
+namespace svgio::svgreader
+{
+ SvgTextNode::SvgTextNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgTspanNode(SVGToken::Text, rDocument, pParent)
+ {
+ }
+
+ SvgTextNode::~SvgTextNode()
+ {
+ }
+
+ void SvgTextNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgTspanNode::parseAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgTextNode::addTextPrimitives(
+ const SvgNode& rCandidate,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ drawinglayer::primitive2d::Primitive2DContainer&& rSource)
+ {
+ if(rSource.empty())
+ return;
+
+ const SvgStyleAttributes* pAttributes = rCandidate.getSvgStyleAttributes();
+
+ if(pAttributes)
+ {
+ // add text with taking all Fill/Stroke attributes into account
+ pAttributes->add_text(rTarget, std::move(rSource));
+ }
+ else
+ {
+ // should not happen, every subnode from SvgTextNode will at least
+ // return the attributes from SvgTextNode. Nonetheless, add text
+ rTarget.append(std::move(rSource));
+ }
+ }
+
+ void SvgTextNode::DecomposeChild(const SvgNode& rCandidate, drawinglayer::primitive2d::Primitive2DContainer& rTarget, SvgTextPosition& rSvgTextPosition) const
+ {
+ switch(rCandidate.getType())
+ {
+ case SVGToken::Character:
+ {
+ // direct SvgTextPathNode derivates, decompose them
+ const SvgCharacterNode& rSvgCharacterNode = static_cast< const SvgCharacterNode& >(rCandidate);
+ rSvgCharacterNode.decomposeText(rTarget, rSvgTextPosition);
+ break;
+ }
+ case SVGToken::TextPath:
+ {
+ // direct TextPath decompose
+ const SvgTextPathNode& rSvgTextPathNode = static_cast< const SvgTextPathNode& >(rCandidate);
+ const auto& rChildren = rSvgTextPathNode.getChildren();
+ const sal_uInt32 nCount(rChildren.size());
+
+ if(nCount && rSvgTextPathNode.isValid())
+ {
+ // remember original TextStart to later detect hor/ver offsets
+ const basegfx::B2DPoint aTextStart(rSvgTextPosition.getPosition());
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ // decompose to regular TextPrimitives
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ DecomposeChild(*rChildren[a], aNewTarget, rSvgTextPosition);
+ }
+
+ if(!aNewTarget.empty())
+ {
+ const drawinglayer::primitive2d::Primitive2DContainer aPathContent(aNewTarget);
+ aNewTarget.clear();
+
+ // dismantle TextPrimitives and map them on curve/path
+ rSvgTextPathNode.decomposePathNode(aPathContent, aNewTarget, aTextStart);
+ }
+
+ if(!aNewTarget.empty())
+ {
+ addTextPrimitives(rCandidate, rTarget, std::move(aNewTarget));
+ }
+ }
+
+ break;
+ }
+ case SVGToken::Tspan:
+ {
+ // Tspan may have children, call recursively
+ const SvgTspanNode& rSvgTspanNode = static_cast< const SvgTspanNode& >(rCandidate);
+ const auto& rChildren = rSvgTspanNode.getChildren();
+ const sal_uInt32 nCount(rChildren.size());
+
+ if(nCount)
+ {
+ SvgTextPosition aSvgTextPosition(&rSvgTextPosition, rSvgTspanNode);
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ DecomposeChild(*rChildren[a], aNewTarget, aSvgTextPosition);
+ }
+
+ rSvgTextPosition.setPosition(aSvgTextPosition.getPosition());
+
+ if(!aNewTarget.empty())
+ {
+ addTextPrimitives(rCandidate, rTarget, std::move(aNewTarget));
+ }
+ }
+ break;
+ }
+ case SVGToken::Tref:
+ {
+ const SvgTrefNode& rSvgTrefNode = static_cast< const SvgTrefNode& >(rCandidate);
+ const SvgTextNode* pRefText = rSvgTrefNode.getReferencedSvgTextNode();
+
+ if(pRefText)
+ {
+ const auto& rChildren = pRefText->getChildren();
+ const sal_uInt32 nCount(rChildren.size());
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+
+ if(nCount)
+ {
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const SvgNode& rChildCandidate = *rChildren[a];
+ const_cast< SvgNode& >(rChildCandidate).setAlternativeParent(this);
+
+ DecomposeChild(rChildCandidate, aNewTarget, rSvgTextPosition);
+ const_cast< SvgNode& >(rChildCandidate).setAlternativeParent();
+ }
+
+ if(!aNewTarget.empty())
+ {
+ addTextPrimitives(rCandidate, rTarget, std::move(aNewTarget));
+ }
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ OSL_ENSURE(false, "Unexpected node in text token (!)");
+ break;
+ }
+ }
+ }
+
+ void SvgTextNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced`*/) const
+ {
+ // text has a group of child nodes, allowed are SVGToken::Character, SVGToken::Tspan,
+ // SVGToken::Tref and SVGToken::TextPath. These increase a given current text position
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(!pStyle || getChildren().empty())
+ return;
+
+ const double fOpacity(pStyle->getOpacity().getNumber());
+
+ if(fOpacity <= 0.0)
+ return;
+
+ SvgTextPosition aSvgTextPosition(nullptr, *this);
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+ const auto& rChildren = getChildren();
+ const sal_uInt32 nCount(rChildren.size());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const SvgNode& rCandidate = *rChildren[a];
+
+ DecomposeChild(rCandidate, aNewTarget, aSvgTextPosition);
+ }
+
+ if(!aNewTarget.empty())
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget2;
+
+ addTextPrimitives(*this, aNewTarget2, std::move(aNewTarget));
+ aNewTarget = aNewTarget2;
+ }
+
+ if(!aNewTarget.empty())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), getTransform());
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtextpathnode.cxx b/svgio/source/svgreader/svgtextpathnode.cxx
new file mode 100644
index 0000000000..9ccdb93f59
--- /dev/null
+++ b/svgio/source/svgreader/svgtextpathnode.cxx
@@ -0,0 +1,436 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtextpathnode.hxx>
+#include <svgstyleattributes.hxx>
+#include <svgpathnode.hxx>
+#include <svgdocument.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <drawinglayer/primitive2d/textbreakuphelper.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/curve/b2dbeziertools.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ namespace {
+
+ class pathTextBreakupHelper : public drawinglayer::primitive2d::TextBreakupHelper
+ {
+ private:
+ const basegfx::B2DPolygon& mrPolygon;
+ const double mfBasegfxPathLength;
+ double mfPosition;
+ const basegfx::B2DPoint& mrTextStart;
+
+ const sal_uInt32 mnMaxIndex;
+ sal_uInt32 mnIndex;
+ basegfx::B2DCubicBezier maCurrentSegment;
+ std::unique_ptr<basegfx::B2DCubicBezierHelper> mpB2DCubicBezierHelper;
+ double mfCurrentSegmentLength;
+ double mfSegmentStartPosition;
+
+ protected:
+ /// allow user callback to allow changes to the new TextTransformation. Default
+ /// does nothing.
+ virtual bool allowChange(sal_uInt32 nCount, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength) override;
+
+ void freeB2DCubicBezierHelper();
+ basegfx::B2DCubicBezierHelper* getB2DCubicBezierHelper();
+ void advanceToPosition(double fNewPosition);
+
+ public:
+ pathTextBreakupHelper(
+ const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
+ const basegfx::B2DPolygon& rPolygon,
+ const double fBasegfxPathLength,
+ double fPosition,
+ const basegfx::B2DPoint& rTextStart);
+ virtual ~pathTextBreakupHelper() override;
+
+ // read access to evtl. advanced position
+ double getPosition() const { return mfPosition; }
+ };
+
+ }
+
+ void pathTextBreakupHelper::freeB2DCubicBezierHelper()
+ {
+ mpB2DCubicBezierHelper.reset();
+ }
+
+ basegfx::B2DCubicBezierHelper* pathTextBreakupHelper::getB2DCubicBezierHelper()
+ {
+ if(!mpB2DCubicBezierHelper && maCurrentSegment.isBezier())
+ {
+ mpB2DCubicBezierHelper.reset(new basegfx::B2DCubicBezierHelper(maCurrentSegment));
+ }
+
+ return mpB2DCubicBezierHelper.get();
+ }
+
+ void pathTextBreakupHelper::advanceToPosition(double fNewPosition)
+ {
+ while(mfSegmentStartPosition + mfCurrentSegmentLength < fNewPosition && mnIndex < mnMaxIndex)
+ {
+ mfSegmentStartPosition += mfCurrentSegmentLength;
+ mnIndex++;
+
+ if(mnIndex < mnMaxIndex)
+ {
+ freeB2DCubicBezierHelper();
+ mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
+ maCurrentSegment.testAndSolveTrivialBezier();
+ mfCurrentSegmentLength = getB2DCubicBezierHelper()
+ ? getB2DCubicBezierHelper()->getLength()
+ : maCurrentSegment.getLength();
+ }
+ }
+
+ mfPosition = fNewPosition;
+ }
+
+ pathTextBreakupHelper::pathTextBreakupHelper(
+ const drawinglayer::primitive2d::TextSimplePortionPrimitive2D& rSource,
+ const basegfx::B2DPolygon& rPolygon,
+ const double fBasegfxPathLength,
+ double fPosition,
+ const basegfx::B2DPoint& rTextStart)
+ : drawinglayer::primitive2d::TextBreakupHelper(rSource),
+ mrPolygon(rPolygon),
+ mfBasegfxPathLength(fBasegfxPathLength),
+ mfPosition(0.0),
+ mrTextStart(rTextStart),
+ mnMaxIndex(rPolygon.isClosed() ? rPolygon.count() : rPolygon.count() - 1),
+ mnIndex(0),
+ mfCurrentSegmentLength(0.0),
+ mfSegmentStartPosition(0.0)
+ {
+ mrPolygon.getBezierSegment(mnIndex % mrPolygon.count(), maCurrentSegment);
+ mfCurrentSegmentLength = maCurrentSegment.getLength();
+
+ advanceToPosition(fPosition);
+ }
+
+ pathTextBreakupHelper::~pathTextBreakupHelper()
+ {
+ freeB2DCubicBezierHelper();
+ }
+
+ bool pathTextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& rNewTransform, sal_uInt32 nIndex, sal_uInt32 nLength)
+ {
+ bool bRetval(false);
+
+ if(mfPosition < mfBasegfxPathLength && nLength && mnIndex < mnMaxIndex)
+ {
+ const double fSnippetWidth(
+ getTextLayouter().getTextWidth(
+ getSource().getText(),
+ nIndex,
+ nLength));
+
+ if(basegfx::fTools::more(fSnippetWidth, 0.0))
+ {
+ const OUString aText(getSource().getText());
+ const std::u16string_view aTrimmedChars(o3tl::trim(aText.subView(nIndex, nLength)));
+ const double fEndPos(mfPosition + fSnippetWidth);
+
+ if(!aTrimmedChars.empty() && (mfPosition < mfBasegfxPathLength || fEndPos > 0.0))
+ {
+ const double fHalfSnippetWidth(fSnippetWidth * 0.5);
+
+ advanceToPosition(mfPosition + fHalfSnippetWidth);
+
+ // create representation for this snippet
+ bRetval = true;
+
+ // get target position and tangent in that point
+ basegfx::B2DPoint aPosition(0.0, 0.0);
+ basegfx::B2DVector aTangent(0.0, 1.0);
+
+ if(mfPosition < 0.0)
+ {
+ // snippet center is left of first segment, but right edge is on it (SVG allows that)
+ aTangent = maCurrentSegment.getTangent(0.0);
+ aTangent.normalize();
+ aPosition = maCurrentSegment.getStartPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
+ }
+ else if(mfPosition > mfBasegfxPathLength)
+ {
+ // snippet center is right of last segment, but left edge is on it (SVG allows that)
+ aTangent = maCurrentSegment.getTangent(1.0);
+ aTangent.normalize();
+ aPosition = maCurrentSegment.getEndPoint() + (aTangent * (mfPosition - mfSegmentStartPosition));
+ }
+ else
+ {
+ // snippet center inside segment, interpolate
+ double fBezierDistance(mfPosition - mfSegmentStartPosition);
+
+ if(getB2DCubicBezierHelper())
+ {
+ // use B2DCubicBezierHelper to bridge the non-linear gap between
+ // length and bezier distances (if it's a bezier segment)
+ fBezierDistance = getB2DCubicBezierHelper()->distanceToRelative(fBezierDistance);
+ }
+ else
+ {
+ // linear relationship, make relative to segment length
+ fBezierDistance = fBezierDistance / mfCurrentSegmentLength;
+ }
+
+ aPosition = maCurrentSegment.interpolatePoint(fBezierDistance);
+ aTangent = maCurrentSegment.getTangent(fBezierDistance);
+ aTangent.normalize();
+ }
+
+ // detect evtl. hor/ver translations (depends on text direction)
+ const basegfx::B2DPoint aBasePoint(rNewTransform * basegfx::B2DPoint(0.0, 0.0));
+ const basegfx::B2DVector aOffset(aBasePoint - mrTextStart);
+
+ if(!basegfx::fTools::equalZero(aOffset.getY()))
+ {
+ // ...and apply
+ aPosition.setY(aPosition.getY() + aOffset.getY());
+ }
+
+ // move target position from snippet center to left text start
+ aPosition -= fHalfSnippetWidth * aTangent;
+
+ // remove current translation
+ rNewTransform.translate(-aBasePoint.getX(), -aBasePoint.getY());
+
+ // rotate due to tangent
+ rNewTransform.rotate(atan2(aTangent.getY(), aTangent.getX()));
+
+ // add new translation
+ rNewTransform.translate(aPosition.getX(), aPosition.getY());
+ }
+
+ // advance to end
+ advanceToPosition(fEndPos);
+ }
+ }
+
+ return bRetval;
+ }
+
+} // end of namespace svgio::svgreader
+
+
+namespace svgio::svgreader
+{
+ SvgTextPathNode::SvgTextPathNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::TextPath, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgTextPathNode::~SvgTextPathNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgTextPathNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgTextPathNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::StartOffset:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maStartOffset = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Method:
+ {
+ break;
+ }
+ case SVGToken::Spacing:
+ {
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ readLocalLink(aContent, maXLink);
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ bool SvgTextPathNode::isValid() const
+ {
+ const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
+
+ if(!pSvgPathNode)
+ {
+ return false;
+ }
+
+ const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
+
+ if(!pPolyPolyPath || !pPolyPolyPath->count())
+ {
+ return false;
+ }
+
+ const basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
+
+ if(!aPolygon.count())
+ {
+ return false;
+ }
+
+ const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
+
+ return !basegfx::fTools::equalZero(fBasegfxPathLength);
+ }
+
+ void SvgTextPathNode::decomposePathNode(
+ const drawinglayer::primitive2d::Primitive2DContainer& rPathContent,
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+ const basegfx::B2DPoint& rTextStart) const
+ {
+ if(rPathContent.empty())
+ return;
+
+ const SvgPathNode* pSvgPathNode = dynamic_cast< const SvgPathNode* >(getDocument().findSvgNodeById(maXLink));
+
+ if(!pSvgPathNode)
+ return;
+
+ const std::optional<basegfx::B2DPolyPolygon>& pPolyPolyPath = pSvgPathNode->getPath();
+
+ if(!(pPolyPolyPath && pPolyPolyPath->count()))
+ return;
+
+ basegfx::B2DPolygon aPolygon(pPolyPolyPath->getB2DPolygon(0));
+
+ if(pSvgPathNode->getTransform())
+ {
+ aPolygon.transform(*pSvgPathNode->getTransform());
+ }
+
+ const double fBasegfxPathLength(basegfx::utils::getLength(aPolygon));
+
+ if(basegfx::fTools::equalZero(fBasegfxPathLength))
+ return;
+
+ double fUserToBasegfx(1.0); // multiply: user->basegfx, divide: basegfx->user
+
+ if(pSvgPathNode->getPathLength().isSet())
+ {
+ const double fUserLength(pSvgPathNode->getPathLength().solve(*this));
+
+ if(fUserLength > 0.0 && !basegfx::fTools::equal(fUserLength, fBasegfxPathLength))
+ {
+ fUserToBasegfx = fUserLength / fBasegfxPathLength;
+ }
+ }
+
+ double fPosition(0.0);
+
+ if(getStartOffset().isSet())
+ {
+ if (SvgUnit::percent == getStartOffset().getUnit())
+ {
+ // percent are relative to path length
+ fPosition = getStartOffset().getNumber() * 0.01 * fBasegfxPathLength;
+ }
+ else
+ {
+ fPosition = getStartOffset().solve(*this) * fUserToBasegfx;
+ }
+ }
+
+ if(fPosition < 0.0)
+ return;
+
+ const sal_Int32 nLength(rPathContent.size());
+ sal_Int32 nCurrent(0);
+
+ while(fPosition < fBasegfxPathLength && nCurrent < nLength)
+ {
+ const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pCandidate = nullptr;
+ const drawinglayer::primitive2d::Primitive2DReference xReference(rPathContent[nCurrent]);
+
+ if(xReference.is())
+ {
+ pCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(xReference.get());
+ }
+
+ if(pCandidate)
+ {
+ pathTextBreakupHelper aPathTextBreakupHelper(
+ *pCandidate,
+ aPolygon,
+ fBasegfxPathLength,
+ fPosition,
+ rTextStart);
+
+ drawinglayer::primitive2d::Primitive2DContainer aResult =
+ aPathTextBreakupHelper.extractResult();
+
+ if(!aResult.empty())
+ {
+ rTarget.append(std::move(aResult));
+ }
+
+ // advance position to consumed
+ fPosition = aPathTextBreakupHelper.getPosition();
+ }
+
+ nCurrent++;
+ }
+ }
+
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtextposition.cxx b/svgio/source/svgreader/svgtextposition.cxx
new file mode 100644
index 0000000000..50a896ba22
--- /dev/null
+++ b/svgio/source/svgreader/svgtextposition.cxx
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtextposition.hxx>
+
+using namespace drawinglayer::primitive2d;
+
+namespace svgio::svgreader
+{
+SvgTextPosition::SvgTextPosition(SvgTextPosition* pParent, const SvgTspanNode& rSvgTspanNode)
+ : mpParent(pParent)
+ , maRotate(solveSvgNumberVector(rSvgTspanNode.getRotate(), rSvgTspanNode))
+ , mfTextLength(0.0)
+ , mnRotationIndex(0)
+ , mbLengthAdjust(rSvgTspanNode.getLengthAdjust())
+ , mbAbsoluteX(false)
+{
+ const InfoProvider& rInfoProvider(rSvgTspanNode);
+
+ // get TextLength if provided
+ if (rSvgTspanNode.getTextLength().isSet())
+ {
+ mfTextLength = rSvgTspanNode.getTextLength().solve(rInfoProvider);
+ }
+
+ // SVG does not really define in which units a \91rotate\92 for Text/TSpan is given,
+ // but it seems to be degrees. Convert here to radians
+ if (!maRotate.empty())
+ {
+ for (double& f : maRotate)
+ {
+ f = basegfx::deg2rad(f);
+ }
+ }
+
+ // get text positions X
+ const sal_uInt32 nSizeX(rSvgTspanNode.getX().size());
+
+ if (nSizeX)
+ {
+ // we have absolute positions, get first one as current text position X
+ maPosition.setX(rSvgTspanNode.getX()[0].solve(rInfoProvider, NumberType::xcoordinate));
+ mbAbsoluteX = true;
+ }
+ else
+ {
+ // no absolute position, get from parent
+ if (pParent)
+ {
+ maPosition.setX(pParent->getPosition().getX());
+ }
+ }
+
+ const sal_uInt32 nSizeDx(rSvgTspanNode.getDx().size());
+ if (nSizeDx)
+ {
+ // relative positions given, translate position derived from parent
+ maPosition.setX(maPosition.getX()
+ + rSvgTspanNode.getDx()[0].solve(rInfoProvider, NumberType::xcoordinate));
+ }
+
+ // fill deltas to maX
+ maX.reserve(nSizeX);
+
+ for (sal_uInt32 a(1); a < std::max(nSizeX, nSizeDx); ++a)
+ {
+ if (a < nSizeX)
+ {
+ double nPos = rSvgTspanNode.getX()[a].solve(rInfoProvider, NumberType::xcoordinate)
+ - maPosition.getX();
+
+ if (a < nSizeDx)
+ {
+ nPos += rSvgTspanNode.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate);
+ }
+
+ maX.push_back(nPos);
+ }
+ else
+ {
+ // Apply them later since it also needs the character width to calculate
+ // the final character position
+ maDx.push_back(rSvgTspanNode.getDx()[a].solve(rInfoProvider, NumberType::xcoordinate));
+ }
+ }
+
+ // get text positions Y
+ const sal_uInt32 nSizeY(rSvgTspanNode.getY().size());
+
+ if (nSizeY)
+ {
+ // we have absolute positions, get first one as current text position Y
+ maPosition.setY(rSvgTspanNode.getY()[0].solve(rInfoProvider, NumberType::ycoordinate));
+ mbAbsoluteX = true;
+ }
+ else
+ {
+ // no absolute position, get from parent
+ if (pParent)
+ {
+ maPosition.setY(pParent->getPosition().getY());
+ }
+ }
+
+ const sal_uInt32 nSizeDy(rSvgTspanNode.getDy().size());
+
+ if (nSizeDy)
+ {
+ // relative positions given, translate position derived from parent
+ maPosition.setY(maPosition.getY()
+ + rSvgTspanNode.getDy()[0].solve(rInfoProvider, NumberType::ycoordinate));
+ }
+
+ // fill deltas to maY
+ maY.reserve(nSizeY);
+
+ for (sal_uInt32 a(1); a < nSizeY; a++)
+ {
+ double nPos = rSvgTspanNode.getY()[a].solve(rInfoProvider, NumberType::ycoordinate)
+ - maPosition.getY();
+
+ if (a < nSizeDy)
+ {
+ nPos += rSvgTspanNode.getDy()[a].solve(rInfoProvider, NumberType::ycoordinate);
+ }
+
+ maY.push_back(nPos);
+ }
+}
+
+bool SvgTextPosition::isRotated() const
+{
+ if (maRotate.empty())
+ {
+ if (getParent())
+ {
+ return getParent()->isRotated();
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+}
+
+double SvgTextPosition::consumeRotation()
+{
+ double fRetval(0.0);
+
+ if (maRotate.empty())
+ {
+ if (getParent())
+ {
+ fRetval = mpParent->consumeRotation();
+ }
+ else
+ {
+ fRetval = 0.0;
+ }
+ }
+ else
+ {
+ const sal_uInt32 nSize(maRotate.size());
+
+ if (mnRotationIndex < nSize)
+ {
+ fRetval = maRotate[mnRotationIndex++];
+ }
+ else
+ {
+ fRetval = maRotate[nSize - 1];
+ }
+ }
+
+ return fRetval;
+}
+
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtitledescnode.cxx b/svgio/source/svgreader/svgtitledescnode.cxx
new file mode 100644
index 0000000000..8498762cc6
--- /dev/null
+++ b/svgio/source/svgreader/svgtitledescnode.cxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtitledescnode.hxx>
+#include <osl/diagnose.h>
+
+
+namespace svgio::svgreader
+{
+ SvgTitleDescNode::SvgTitleDescNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent)
+ {
+ OSL_ENSURE(aType == SVGToken::Title || aType == SVGToken::Desc, "SvgTitleDescNode should only be used for Title and Desc (!)");
+ }
+
+ SvgTitleDescNode::~SvgTitleDescNode()
+ {
+ }
+
+ void SvgTitleDescNode::concatenate(std::u16string_view rChars)
+ {
+ maText += rChars;
+ }
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtoken.cxx b/svgio/source/svgreader/svgtoken.cxx
new file mode 100644
index 0000000000..fa28c8647c
--- /dev/null
+++ b/svgio/source/svgreader/svgtoken.cxx
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtoken.hxx>
+#include <frozen/bits/defines.h>
+#include <frozen/bits/elsa_std.h>
+#include <frozen/unordered_map.h>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+
+constexpr auto aSVGTokenMap = frozen::make_unordered_map<std::u16string_view, SVGToken>(
+{
+ { u"width", SVGToken::Width },
+ { u"height", SVGToken::Height },
+ { u"viewBox", SVGToken::ViewBox },
+ { u"transform", SVGToken::Transform },
+ { u"style", SVGToken::Style },
+ { u"display", SVGToken::Display }, // #i121656#
+ { u"d", SVGToken::D },
+ { u"x", SVGToken::X },
+ { u"y", SVGToken::Y },
+ { u"xmlns", SVGToken::Xmlns },
+ { u"version", SVGToken::Version },
+ { u"id", SVGToken::Id },
+ { u"in", SVGToken::In },
+ { u"rx", SVGToken::Rx },
+ { u"ry", SVGToken::Ry },
+ { u"points", SVGToken::Points },
+ { u"dx", SVGToken::Dx },
+ { u"dy", SVGToken::Dy },
+ { u"rotate", SVGToken::Rotate },
+ { u"textLength", SVGToken::TextLength },
+ { u"lengthAdjust", SVGToken::LengthAdjust },
+ { u"font", SVGToken::Font },
+ { u"font-family", SVGToken::FontFamily },
+ { u"font-size", SVGToken::FontSize },
+ { u"font-size-adjust", SVGToken::FontSizeAdjust },
+ { u"font-stretch", SVGToken::FontStretch },
+ { u"font-style", SVGToken::FontStyle },
+ { u"font-variant", SVGToken::FontVariant },
+ { u"font-weight", SVGToken::FontWeight },
+ { u"direction", SVGToken::Direction },
+ { u"letter-spacing", SVGToken::LetterSpacing },
+ { u"text-decoration", SVGToken::TextDecoration },
+ { u"unicode-bidi", SVGToken::UnicodeBidi },
+ { u"word-spacing", SVGToken::WordSpacing },
+ { u"tspan", SVGToken::Tspan },
+ { u"tref", SVGToken::Tref },
+ { u"textPath", SVGToken::TextPath },
+ { u"startOffset", SVGToken::StartOffset },
+ { u"method", SVGToken::Method },
+ { u"spacing", SVGToken::Spacing },
+ { u"stdDeviation", SVGToken::StdDeviation },
+ { u"text-align", SVGToken::TextAlign },
+ { u"pathLength", SVGToken::PathLength },
+ { u"type", SVGToken::Type },
+ { u"class", SVGToken::Class },
+ { u"text-anchor", SVGToken::TextAnchor },
+ { u"xml:space", SVGToken::XmlSpace },
+ { u"color", SVGToken::Color },
+ { u"clipPath", SVGToken::ClipPathNode },
+ { u"clip-path", SVGToken::ClipPathProperty },
+ { u"feColorMatrix", SVGToken::FeColorMatrix },
+ { u"feDropShadow", SVGToken::FeDropShadow },
+ { u"feFlood", SVGToken::FeFlood },
+ { u"feImage", SVGToken::FeImage },
+ { u"feGaussianBlur", SVGToken::FeGaussianBlur },
+ { u"feOffset", SVGToken::FeOffset },
+ { u"filter", SVGToken::Filter },
+ { u"flood-color", SVGToken::FloodColor },
+ { u"flood-opacity", SVGToken::FloodOpacity },
+ { u"mask", SVGToken::Mask },
+ { u"clipPathUnits", SVGToken::ClipPathUnits },
+ { u"maskUnits", SVGToken::MaskUnits },
+ { u"maskContentUnits", SVGToken::MaskContentUnits },
+ { u"clip-rule", SVGToken::ClipRule },
+ { u"marker", SVGToken::Marker },
+ { u"marker-start", SVGToken::MarkerStart },
+ { u"marker-mid", SVGToken::MarkerMid },
+ { u"marker-end", SVGToken::MarkerEnd },
+ { u"refX", SVGToken::RefX },
+ { u"refY", SVGToken::RefY },
+ { u"markerUnits", SVGToken::MarkerUnits },
+ { u"markerWidth", SVGToken::MarkerWidth },
+ { u"markerHeight", SVGToken::MarkerHeight },
+ { u"orient", SVGToken::Orient },
+ { u"pattern", SVGToken::Pattern },
+ { u"patternUnits", SVGToken::PatternUnits },
+ { u"patternContentUnits", SVGToken::PatternContentUnits },
+ { u"patternTransform", SVGToken::PatternTransform },
+ { u"opacity", SVGToken::Opacity },
+ { u"visibility", SVGToken::Visibility },
+ { u"title", SVGToken::Title },
+ { u"desc", SVGToken::Desc },
+ { u"preserveAspectRatio", SVGToken::PreserveAspectRatio },
+ { u"defer", SVGToken::Defer },
+ { u"none", SVGToken::None },
+ { u"xMinYMin", SVGToken::XMinYMin },
+ { u"xMidYMin", SVGToken::XMidYMin },
+ { u"xMaxYMin", SVGToken::XMaxYMin },
+ { u"xMinYMid", SVGToken::XMinYMid },
+ { u"xMidYMid", SVGToken::XMidYMid },
+ { u"xMaxYMid", SVGToken::XMaxYMid },
+ { u"xMinYMax", SVGToken::XMinYMax },
+ { u"xMidYMax", SVGToken::XMidYMax },
+ { u"xMaxYMax", SVGToken::XMaxYMax },
+ { u"meet", SVGToken::Meet },
+ { u"slice", SVGToken::Slice },
+ { u"values", SVGToken::Values },
+
+ { u"defs", SVGToken::Defs },
+ { u"g", SVGToken::G },
+ { u"svg", SVGToken::Svg },
+ { u"symbol", SVGToken::Symbol },
+ { u"switch", SVGToken::Switch },
+ { u"use", SVGToken::Use },
+ { u"a", SVGToken::A },
+
+ { u"circle", SVGToken::Circle },
+ { u"ellipse", SVGToken::Ellipse },
+ { u"line", SVGToken::Line },
+ { u"path", SVGToken::Path },
+ { u"polygon", SVGToken::Polygon },
+ { u"polyline", SVGToken::Polyline },
+ { u"rect", SVGToken::Rect },
+ { u"image", SVGToken::Image },
+
+ { u"linearGradient", SVGToken::LinearGradient },
+ { u"radialGradient", SVGToken::RadialGradient },
+ { u"stop", SVGToken::Stop },
+ { u"offset", SVGToken::Offset },
+ { u"x1", SVGToken::X1 },
+ { u"y1", SVGToken::Y1 },
+ { u"x2", SVGToken::X2 },
+ { u"y2", SVGToken::Y2 },
+ { u"cx", SVGToken::Cx },
+ { u"cy", SVGToken::Cy },
+ { u"fx", SVGToken::Fx },
+ { u"fy", SVGToken::Fy },
+ { u"r", SVGToken::R },
+ { u"gradientUnits", SVGToken::GradientUnits },
+ { u"gradientTransform", SVGToken::GradientTransform },
+ { u"spreadMethod", SVGToken::SpreadMethod },
+ { u"href", SVGToken::Href },
+ { u"xlink:href", SVGToken::XlinkHref },
+ { u"stop-color", SVGToken::StopColor },
+ { u"stop-opacity", SVGToken::StopOpacity },
+
+ { u"fill", SVGToken::Fill },
+ { u"fill-opacity", SVGToken::FillOpacity },
+ { u"fill-rule", SVGToken::FillRule },
+
+ { u"stroke", SVGToken::Stroke },
+ { u"stroke-dasharray", SVGToken::StrokeDasharray },
+ { u"stroke-dashoffset", SVGToken::StrokeDashoffset },
+ { u"stroke-linecap", SVGToken::StrokeLinecap },
+ { u"stroke-linejoin", SVGToken::StrokeLinejoin },
+ { u"stroke-miterlimit", SVGToken::StrokeMiterlimit },
+ { u"stroke-opacity", SVGToken::StrokeOpacity },
+ { u"stroke-width", SVGToken::StrokeWidth },
+
+ { u"text", SVGToken::Text },
+ { u"baseline-shift", SVGToken::BaselineShift },
+ { u"dominant-baseline", SVGToken::DominantBaseline }
+});
+
+SVGToken StrToSVGToken(std::u16string_view rStr, bool bIgnoreCase)
+{
+ std::u16string_view aStr = rStr.starts_with(u"svg:") ? rStr.substr(4) : rStr;
+
+ // TODO: a better alternative to the bIgnoreCase would be separate maps for SVG and CSS,
+ // the latter using case-insensitive hasher and comparator, with separate search functions.
+
+ auto it = bIgnoreCase ? std::find_if(aSVGTokenMap.begin(), aSVGTokenMap.end(),
+ [aStr](const auto& el)
+ { return o3tl::equalsIgnoreAsciiCase(el.first, aStr); })
+ : aSVGTokenMap.find(aStr);
+ if (it != aSVGTokenMap.end())
+ return it->second;
+
+ return SVGToken::Unknown;
+}
+
+OUString SVGTokenToStr(const SVGToken& rToken)
+{
+ auto it = std::find_if(aSVGTokenMap.begin(), aSVGTokenMap.end(),
+ [rToken](const auto& el) { return el.second == rToken; });
+ if (it != aSVGTokenMap.end())
+ return OUString(it->first);
+
+ return OUString();
+}
+
+} // end of namespace svgio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtools.cxx b/svgio/source/svgreader/svgtools.cxx
new file mode 100644
index 0000000000..999de30753
--- /dev/null
+++ b/svgio/source/svgreader/svgtools.cxx
@@ -0,0 +1,1516 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtools.hxx>
+#include <sal/log.hxx>
+#include <tools/color.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <svgtoken.hxx>
+#include <frozen/bits/defines.h>
+#include <frozen/bits/elsa_std.h>
+#include <frozen/unordered_map.h>
+
+namespace svgio::svgreader
+{
+ constexpr auto aColorTokenMapperList = frozen::make_unordered_map<std::u16string_view, Color>(
+ {
+ { u"aliceblue", Color(240, 248, 255) },
+ { u"antiquewhite", Color(250, 235, 215) },
+ { u"aqua", Color( 0, 255, 255) },
+ { u"aquamarine", Color(127, 255, 212) },
+ { u"azure", Color(240, 255, 255) },
+ { u"beige", Color(245, 245, 220) },
+ { u"bisque", Color(255, 228, 196) },
+ { u"black", Color( 0, 0, 0) },
+ { u"blanchedalmond", Color(255, 235, 205) },
+ { u"blue", Color( 0, 0, 255) },
+ { u"blueviolet", Color(138, 43, 226) },
+ { u"brown", Color(165, 42, 42) },
+ { u"burlywood", Color(222, 184, 135) },
+ { u"cadetblue", Color( 95, 158, 160) },
+ { u"chartreuse", Color(127, 255, 0) },
+ { u"chocolate", Color(210, 105, 30) },
+ { u"coral", Color(255, 127, 80) },
+ { u"cornflowerblue", Color(100, 149, 237) },
+ { u"cornsilk", Color(255, 248, 220) },
+ { u"crimson", Color(220, 20, 60) },
+ { u"cyan", Color( 0, 255, 255) },
+ { u"darkblue", Color( 0, 0, 139) },
+ { u"darkcyan", Color( 0, 139, 139) },
+ { u"darkgoldenrod", Color(184, 134, 11) },
+ { u"darkgray", Color(169, 169, 169) },
+ { u"darkgreen", Color( 0, 100, 0) },
+ { u"darkgrey", Color(169, 169, 169) },
+ { u"darkkhaki", Color(189, 183, 107) },
+ { u"darkmagenta", Color(139, 0, 139) },
+ { u"darkolivegreen", Color( 85, 107, 47) },
+ { u"darkorange", Color(255, 140, 0) },
+ { u"darkorchid", Color(153, 50, 204) },
+ { u"darkred", Color(139, 0, 0) },
+ { u"darksalmon", Color(233, 150, 122) },
+ { u"darkseagreen", Color(143, 188, 143) },
+ { u"darkslateblue", Color( 72, 61, 139) },
+ { u"darkslategray", Color( 47, 79, 79) },
+ { u"darkslategrey", Color( 47, 79, 79) },
+ { u"darkturquoise", Color( 0, 206, 209) },
+ { u"darkviolet", Color(148, 0, 211) },
+ { u"deeppink", Color(255, 20, 147) },
+ { u"deepskyblue", Color( 0, 191, 255) },
+ { u"dimgray", Color(105, 105, 105) },
+ { u"dimgrey", Color(105, 105, 105) },
+ { u"dodgerblue", Color( 30, 144, 255) },
+ { u"firebrick", Color(178, 34, 34) },
+ { u"floralwhite", Color(255, 250, 240) },
+ { u"forestgreen", Color( 34, 139, 34) },
+ { u"fuchsia", Color(255, 0, 255) },
+ { u"gainsboro", Color(220, 220, 220) },
+ { u"ghostwhite", Color(248, 248, 255) },
+ { u"gold", Color(255, 215, 0) },
+ { u"goldenrod", Color(218, 165, 32) },
+ { u"gray", Color(128, 128, 128) },
+ { u"grey", Color(128, 128, 128) },
+ { u"green", Color(0, 128, 0) },
+ { u"greenyellow", Color(173, 255, 47) },
+ { u"honeydew", Color(240, 255, 240) },
+ { u"hotpink", Color(255, 105, 180) },
+ { u"indianred", Color(205, 92, 92) },
+ { u"indigo", Color( 75, 0, 130) },
+ { u"ivory", Color(255, 255, 240) },
+ { u"khaki", Color(240, 230, 140) },
+ { u"lavender", Color(230, 230, 250) },
+ { u"lavenderblush", Color(255, 240, 245) },
+ { u"lawngreen", Color(124, 252, 0) },
+ { u"lemonchiffon", Color(255, 250, 205) },
+ { u"lightblue", Color(173, 216, 230) },
+ { u"lightcoral", Color(240, 128, 128) },
+ { u"lightcyan", Color(224, 255, 255) },
+ { u"lightgoldenrodyellow", Color(250, 250, 210) },
+ { u"lightgray", Color(211, 211, 211) },
+ { u"lightgreen", Color(144, 238, 144) },
+ { u"lightgrey", Color(211, 211, 211) },
+ { u"lightpink", Color(255, 182, 193) },
+ { u"lightsalmon", Color(255, 160, 122) },
+ { u"lightseagreen", Color( 32, 178, 170) },
+ { u"lightskyblue", Color(135, 206, 250) },
+ { u"lightslategray", Color(119, 136, 153) },
+ { u"lightslategrey", Color(119, 136, 153) },
+ { u"lightsteelblue", Color(176, 196, 222) },
+ { u"lightyellow", Color(255, 255, 224) },
+ { u"lime", Color( 0, 255, 0) },
+ { u"limegreen", Color( 50, 205, 50) },
+ { u"linen", Color(250, 240, 230) },
+ { u"magenta", Color(255, 0, 255) },
+ { u"maroon", Color(128, 0, 0) },
+ { u"mediumaquamarine", Color(102, 205, 170) },
+ { u"mediumblue", Color( 0, 0, 205) },
+ { u"mediumorchid", Color(186, 85, 211) },
+ { u"mediumpurple", Color(147, 112, 219) },
+ { u"mediumseagreen", Color( 60, 179, 113) },
+ { u"mediumslateblue", Color(123, 104, 238) },
+ { u"mediumspringgreen", Color( 0, 250, 154) },
+ { u"mediumturquoise", Color( 72, 209, 204) },
+ { u"mediumvioletred", Color(199, 21, 133) },
+ { u"midnightblue", Color( 25, 25, 112) },
+ { u"mintcream", Color(245, 255, 250) },
+ { u"mistyrose", Color(255, 228, 225) },
+ { u"moccasin", Color(255, 228, 181) },
+ { u"navajowhite", Color(255, 222, 173) },
+ { u"navy", Color( 0, 0, 128) },
+ { u"oldlace", Color(253, 245, 230) },
+ { u"olive", Color(128, 128, 0) },
+ { u"olivedrab", Color(107, 142, 35) },
+ { u"orange", Color(255, 165, 0) },
+ { u"orangered", Color(255, 69, 0) },
+ { u"orchid", Color(218, 112, 214) },
+ { u"palegoldenrod", Color(238, 232, 170) },
+ { u"palegreen", Color(152, 251, 152) },
+ { u"paleturquoise", Color(175, 238, 238) },
+ { u"palevioletred", Color(219, 112, 147) },
+ { u"papayawhip", Color(255, 239, 213) },
+ { u"peachpuff", Color(255, 218, 185) },
+ { u"peru", Color(205, 133, 63) },
+ { u"pink", Color(255, 192, 203) },
+ { u"plum", Color(221, 160, 221) },
+ { u"powderblue", Color(176, 224, 230) },
+ { u"purple", Color(128, 0, 128) },
+ { u"red", Color(255, 0, 0) },
+ { u"rosybrown", Color(188, 143, 143) },
+ { u"royalblue", Color( 65, 105, 225) },
+ { u"saddlebrown", Color(139, 69, 19) },
+ { u"salmon", Color(250, 128, 114) },
+ { u"sandybrown", Color(244, 164, 96) },
+ { u"seagreen", Color( 46, 139, 87) },
+ { u"seashell", Color(255, 245, 238) },
+ { u"sienna", Color(160, 82, 45) },
+ { u"silver", Color(192, 192, 192) },
+ { u"skyblue", Color(135, 206, 235) },
+ { u"slateblue", Color(106, 90, 205) },
+ { u"slategray", Color(112, 128, 144) },
+ { u"slategrey", Color(112, 128, 144) },
+ { u"snow", Color(255, 250, 250) },
+ { u"springgreen", Color( 0, 255, 127) },
+ { u"steelblue", Color( 70, 130, 180) },
+ { u"tan", Color(210, 180, 140) },
+ { u"teal", Color( 0, 128, 128) },
+ { u"thistle", Color(216, 191, 216) },
+ { u"tomato", Color(255, 99, 71) },
+ { u"turquoise", Color( 64, 224, 208) },
+ { u"violet", Color(238, 130, 238) },
+ { u"wheat", Color(245, 222, 179) },
+ { u"white", Color(255, 255, 255) },
+ { u"whitesmoke", Color(245, 245, 245) },
+ { u"yellow", Color(255, 255, 0) },
+ { u"yellowgreen", Color(154, 205, 50) }
+ });
+
+ basegfx::B2DHomMatrix SvgAspectRatio::createLinearMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource)
+ {
+ basegfx::B2DHomMatrix aRetval;
+ const double fSWidth(rSource.getWidth());
+ const double fSHeight(rSource.getHeight());
+ const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth));
+ const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight));
+
+ // transform from source state to unit range
+ aRetval.translate(-rSource.getMinX(), -rSource.getMinY());
+ aRetval.scale(
+ (bNoSWidth ? 1.0 : 1.0 / fSWidth) * rTarget.getWidth(),
+ (bNoSHeight ? 1.0 : 1.0 / fSHeight) * rTarget.getHeight());
+
+ // transform from unit rage to target range
+ aRetval.translate(rTarget.getMinX(), rTarget.getMinY());
+
+ return aRetval;
+ }
+
+ basegfx::B2DHomMatrix SvgAspectRatio::createMapping(const basegfx::B2DRange& rTarget, const basegfx::B2DRange& rSource) const
+ {
+ // removed !isSet() from below. Due to correct defaults in the constructor an instance
+ // of this class is perfectly useful without being set by any importer
+ if(SvgAlign::none == getSvgAlign())
+ {
+ // create linear mapping (default)
+ return createLinearMapping(rTarget, rSource);
+ }
+
+ basegfx::B2DHomMatrix aRetval;
+
+ const double fSWidth(rSource.getWidth());
+ const double fSHeight(rSource.getHeight());
+ const bool bNoSWidth(basegfx::fTools::equalZero(fSWidth));
+ const bool bNoSHeight(basegfx::fTools::equalZero(fSHeight));
+ const double fScaleX((bNoSWidth ? 1.0 : 1.0 / fSWidth) * rTarget.getWidth());
+ const double fScaleY((bNoSHeight ? 1.0 : 1.0 / fSHeight) * rTarget.getHeight());
+ const double fScale(isMeetOrSlice() ? std::min(fScaleX, fScaleY) : std::max(fScaleX, fScaleY));
+
+ // remove source translation, apply scale
+ aRetval.translate(-rSource.getMinX(), -rSource.getMinY());
+ aRetval.scale(fScale, fScale);
+
+ // evaluate horizontal alignment
+ const double fNewWidth(fSWidth * fScale);
+ double fTransX(0.0);
+
+ switch(getSvgAlign())
+ {
+ case SvgAlign::xMidYMin:
+ case SvgAlign::xMidYMid:
+ case SvgAlign::xMidYMax:
+ {
+ // centerX
+ const double fFreeSpace(rTarget.getWidth() - fNewWidth);
+ fTransX = fFreeSpace * 0.5;
+ break;
+ }
+ case SvgAlign::xMaxYMin:
+ case SvgAlign::xMaxYMid:
+ case SvgAlign::xMaxYMax:
+ {
+ // Right align
+ const double fFreeSpace(rTarget.getWidth() - fNewWidth);
+ fTransX = fFreeSpace;
+ break;
+ }
+ default: break;
+ }
+
+ // evaluate vertical alignment
+ const double fNewHeight(fSHeight * fScale);
+ double fTransY(0.0);
+
+ switch(getSvgAlign())
+ {
+ case SvgAlign::xMinYMid:
+ case SvgAlign::xMidYMid:
+ case SvgAlign::xMaxYMid:
+ {
+ // centerY
+ const double fFreeSpace(rTarget.getHeight() - fNewHeight);
+ fTransY = fFreeSpace * 0.5;
+ break;
+ }
+ case SvgAlign::xMinYMax:
+ case SvgAlign::xMidYMax:
+ case SvgAlign::xMaxYMax:
+ {
+ // Bottom align
+ const double fFreeSpace(rTarget.getHeight() - fNewHeight);
+ fTransY = fFreeSpace;
+ break;
+ }
+ default: break;
+ }
+
+ // add target translation
+ aRetval.translate(
+ rTarget.getMinX() + fTransX,
+ rTarget.getMinY() + fTransY);
+
+ return aRetval;
+ }
+
+ void skip_char(std::u16string_view rCandidate, sal_Unicode nChar, sal_Int32& nPos, const sal_Int32 nLen)
+ {
+ while(nPos < nLen && nChar == rCandidate[nPos])
+ {
+ nPos++;
+ }
+ }
+
+ void skip_char(std::u16string_view rCandidate, sal_Unicode nCharA, sal_Unicode nCharB, sal_Int32& nPos, const sal_Int32 nLen)
+ {
+ while(nPos < nLen && (nCharA == rCandidate[nPos] || nCharB == rCandidate[nPos]))
+ {
+ nPos++;
+ }
+ }
+
+ void copySign(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen)
+ {
+ if(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ if('+' == aChar || '-' == aChar)
+ {
+ rTarget.append(aChar);
+ nPos++;
+ }
+ }
+ }
+
+ void copyNumber(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen)
+ {
+ bool bOnNumber(true);
+
+ while(bOnNumber && nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ bOnNumber = ('0' <= aChar && '9' >= aChar) || '.' == aChar;
+
+ if(bOnNumber)
+ {
+ rTarget.append(aChar);
+ nPos++;
+ }
+ }
+ }
+
+ void copyHex(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen)
+ {
+ bool bOnHex(true);
+
+ while(bOnHex && nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ bOnHex = ('0' <= aChar && '9' >= aChar)
+ || ('A' <= aChar && 'F' >= aChar)
+ || ('a' <= aChar && 'f' >= aChar);
+
+ if(bOnHex)
+ {
+ rTarget.append(aChar);
+ nPos++;
+ }
+ }
+ }
+
+ void copyString(std::u16string_view rCandidate, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen)
+ {
+ bool bOnChar(true);
+
+ while(bOnChar && nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ bOnChar = ('a' <= aChar && 'z' >= aChar)
+ || ('A' <= aChar && 'Z' >= aChar)
+ || '-' == aChar;
+
+ if(bOnChar)
+ {
+ rTarget.append(aChar);
+ nPos++;
+ }
+ }
+ }
+
+ void copyToLimiter(std::u16string_view rCandidate, sal_Unicode nLimiter, sal_Int32& nPos, OUStringBuffer& rTarget, const sal_Int32 nLen)
+ {
+ while(nPos < nLen && nLimiter != rCandidate[nPos])
+ {
+ rTarget.append(rCandidate[nPos]);
+ nPos++;
+ }
+ }
+
+ bool readNumber(std::u16string_view rCandidate, sal_Int32& nPos, double& fNum, const sal_Int32 nLen)
+ {
+ if(nPos < nLen)
+ {
+ OUStringBuffer aNum;
+
+ copySign(rCandidate, nPos, aNum, nLen);
+ copyNumber(rCandidate, nPos, aNum, nLen);
+
+ if(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ if('e' == aChar || 'E' == aChar)
+ {
+ // try to read exponential number, but be careful. I had
+ // a case where dx="2em" was used, thus the 'e' was consumed
+ // by error. First try if there are numbers after the 'e',
+ // safe current state
+ nPos++;
+ const OUStringBuffer aNum2(aNum);
+ const sal_Int32 nPosAfterE(nPos);
+
+ aNum.append(aChar);
+ copySign(rCandidate, nPos, aNum, nLen);
+ copyNumber(rCandidate, nPos, aNum, nLen);
+
+ if(nPosAfterE == nPos)
+ {
+ // no number after 'e', go back. Do not
+ // return false, it's still a valid integer number
+ aNum = aNum2;
+ nPos--;
+ }
+ }
+ }
+
+ if(!aNum.isEmpty())
+ {
+ rtl_math_ConversionStatus eStatus;
+
+ fNum = rtl::math::stringToDouble(
+ aNum, '.', ',',
+ &eStatus);
+
+ return eStatus == rtl_math_ConversionStatus_Ok;
+ }
+ }
+
+ return false;
+ }
+
+ SvgUnit readUnit(std::u16string_view rCandidate, sal_Int32& nPos, const sal_Int32 nLen)
+ {
+ SvgUnit aRetval(SvgUnit::px);
+
+ if(nPos < nLen)
+ {
+ const sal_Unicode aCharA(rCandidate[nPos]);
+
+ if('%' == aCharA)
+ {
+ // percent used, relative to current
+ nPos++;
+ aRetval = SvgUnit::percent;
+ }
+ else if(nPos + 1 < nLen)
+ {
+ const sal_Unicode aCharB(rCandidate[nPos + 1]);
+ bool bTwoCharValid(false);
+
+ switch(aCharA)
+ {
+ case u'e' :
+ {
+ if('m' == aCharB)
+ {
+ // 'em' Relative to current font size
+ aRetval = SvgUnit::em;
+ bTwoCharValid = true;
+ }
+ else if('x' == aCharB)
+ {
+ // 'ex' Relative to current font x-height
+ aRetval = SvgUnit::ex;
+ bTwoCharValid = true;
+ }
+ break;
+ }
+ case u'p' :
+ {
+ if('x' == aCharB)
+ {
+ // 'px' UserUnit (default)
+ bTwoCharValid = true;
+ }
+ else if('t' == aCharB)
+ {
+ // 'pt' == 4/3 px
+ aRetval = SvgUnit::pt;
+ bTwoCharValid = true;
+ }
+ else if('c' == aCharB)
+ {
+ // 'pc' == 16 px
+ aRetval = SvgUnit::pc;
+ bTwoCharValid = true;
+ }
+ break;
+ }
+ case u'i' :
+ {
+ if('n' == aCharB)
+ {
+ // 'in' == 96 px, since CSS 2.1
+ aRetval = SvgUnit::in;
+ bTwoCharValid = true;
+ }
+ break;
+ }
+ case u'c' :
+ {
+ if('m' == aCharB)
+ {
+ // 'cm' == 37.79527559 px
+ aRetval = SvgUnit::cm;
+ bTwoCharValid = true;
+ }
+ break;
+ }
+ case u'm' :
+ {
+ if('m' == aCharB)
+ {
+ // 'mm' == 3.779528 px
+ aRetval = SvgUnit::mm;
+ bTwoCharValid = true;
+ }
+ break;
+ }
+ }
+
+ if(bTwoCharValid)
+ {
+ nPos += 2;
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ bool readNumberAndUnit(std::u16string_view rCandidate, sal_Int32& nPos, SvgNumber& aNum, const sal_Int32 nLen)
+ {
+ double fNum(0.0);
+
+ if(readNumber(rCandidate, nPos, fNum, nLen))
+ {
+ skip_char(rCandidate, ' ', nPos, nLen);
+ aNum = SvgNumber(fNum, readUnit(rCandidate, nPos, nLen));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ bool readAngle(std::u16string_view rCandidate, sal_Int32& nPos, double& fAngle, const sal_Int32 nLen)
+ {
+ if(readNumber(rCandidate, nPos, fAngle, nLen))
+ {
+ skip_char(rCandidate, ' ', nPos, nLen);
+
+ enum class DegreeType
+ {
+ deg,
+ grad,
+ rad
+ } aType(DegreeType::deg); // degrees is default
+
+ if(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+ static constexpr std::u16string_view aStrGrad = u"grad";
+ static constexpr std::u16string_view aStrRad = u"rad";
+
+ switch(aChar)
+ {
+ case u'g' :
+ case u'G' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrGrad, nPos))
+ {
+ // angle in grad
+ nPos += aStrGrad.size();
+ aType = DegreeType::grad;
+ }
+ break;
+ }
+ case u'r' :
+ case u'R' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrRad, nPos))
+ {
+ // angle in radians
+ nPos += aStrRad.size();
+ aType = DegreeType::rad;
+ }
+ break;
+ }
+ }
+ }
+
+ // convert to radians
+ if (DegreeType::deg == aType)
+ {
+ fAngle = basegfx::deg2rad(fAngle);
+ }
+ else if (DegreeType::grad == aType)
+ {
+ // looks like 100 grad is 90 degrees
+ fAngle *= M_PI / 200.0;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ sal_Int32 read_hex(sal_Unicode nChar)
+ {
+ if(nChar >= '0' && nChar <= '9')
+ {
+ return nChar - u'0';
+ }
+ else if(nChar >= 'A' && nChar <= 'F')
+ {
+ return 10 + sal_Int32(nChar - u'A');
+ }
+ else if(nChar >= 'a' && nChar <= 'f')
+ {
+ return 10 + sal_Int32(nChar - u'a');
+ }
+ else
+ {
+ // error
+ return 0;
+ }
+ }
+
+ bool match_colorKeyword(basegfx::BColor& rColor, const OUString& rName)
+ {
+ auto const aResult = aColorTokenMapperList.find(rName.toAsciiLowerCase().trim());
+
+ if(aResult == aColorTokenMapperList.end())
+ {
+ return false;
+ }
+ else
+ {
+ rColor = aResult->second.getBColor();
+ return true;
+ }
+ }
+
+ bool read_color(const OUString& rCandidate, basegfx::BColor& rColor, SvgNumber& rOpacity)
+ {
+ const sal_Int32 nLen(rCandidate.getLength());
+
+ if(nLen)
+ {
+ const sal_Unicode aChar(rCandidate[0]);
+ const double fFactor(1.0 / 255.0);
+
+ if(aChar == '#')
+ {
+ // hex definition
+ OUStringBuffer aNum;
+ sal_Int32 nPos(1);
+
+ copyHex(rCandidate, nPos, aNum, nLen);
+ const sal_Int32 nLength(aNum.getLength());
+
+ if(3 == nLength)
+ {
+ const sal_Int32 nR(read_hex(aNum[0]));
+ const sal_Int32 nG(read_hex(aNum[1]));
+ const sal_Int32 nB(read_hex(aNum[2]));
+
+ rColor.setRed((nR | (nR << 4)) * fFactor);
+ rColor.setGreen((nG | (nG << 4)) * fFactor);
+ rColor.setBlue((nB | (nB << 4)) * fFactor);
+
+ return true;
+ }
+ else if(6 == nLength)
+ {
+ const sal_Int32 nR1(read_hex(aNum[0]));
+ const sal_Int32 nR2(read_hex(aNum[1]));
+ const sal_Int32 nG1(read_hex(aNum[2]));
+ const sal_Int32 nG2(read_hex(aNum[3]));
+ const sal_Int32 nB1(read_hex(aNum[4]));
+ const sal_Int32 nB2(read_hex(aNum[5]));
+
+ rColor.setRed((nR2 | (nR1 << 4)) * fFactor);
+ rColor.setGreen((nG2 | (nG1 << 4)) * fFactor);
+ rColor.setBlue((nB2 | (nB1 << 4)) * fFactor);
+
+ return true;
+ }
+ }
+ else
+ {
+ static const char aStrRgb[] = "rgb";
+
+ if(rCandidate.matchIgnoreAsciiCase(aStrRgb, 0))
+ {
+ // rgb/rgba definition
+ sal_Int32 nPos(strlen(aStrRgb));
+ bool bIsRGBA = false;
+
+ if('a' == rCandidate[nPos])
+ {
+ //Delete the 'a' from 'rbga'
+ skip_char(rCandidate, 'a', nPos, nPos + 1);
+ bIsRGBA = true;
+ }
+
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ double fR(0.0);
+
+ if(readNumber(rCandidate, nPos, fR, nLen))
+ {
+ skip_char(rCandidate, ' ', nPos, nLen);
+
+ if(nPos < nLen)
+ {
+ const sal_Unicode aPercentChar(rCandidate[nPos]);
+ const bool bIsPercent('%' == aPercentChar);
+ double fG(0.0);
+
+ if(bIsPercent)
+ {
+ skip_char(rCandidate, '%', nPos, nLen);
+ }
+
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumber(rCandidate, nPos, fG, nLen))
+ {
+ double fB(0.0);
+
+ if(bIsPercent)
+ {
+ skip_char(rCandidate, '%', nPos, nLen);
+ }
+
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumber(rCandidate, nPos, fB, nLen))
+ {
+ double fA(1.0);
+
+ if(bIsPercent)
+ {
+ skip_char(rCandidate, '%', nPos, nLen);
+ }
+
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumber(rCandidate, nPos, fA, nLen))
+ {
+ if(bIsRGBA)
+ {
+ const double fFac(bIsPercent ? 0.01 : 1);
+ rOpacity = SvgNumber(fA * fFac);
+
+ if(bIsPercent)
+ {
+ skip_char(rCandidate, '%', nPos, nLen);
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ const double fFac(bIsPercent ? 0.01 : fFactor);
+
+ rColor.setRed(fR * fFac);
+ rColor.setGreen(fG * fFac);
+ rColor.setBlue(fB * fFac);
+
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // color keyword
+ if(match_colorKeyword(rColor, rCandidate))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ basegfx::B2DRange readViewBox(std::u16string_view rCandidate, InfoProvider const & rInfoProvider)
+ {
+ const sal_Int32 nLen(rCandidate.size());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ SvgNumber aMinX;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aMinX, nLen))
+ {
+ SvgNumber aMinY;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aMinY, nLen))
+ {
+ SvgNumber aWidth;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aWidth, nLen))
+ {
+ SvgNumber aHeight;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aHeight, nLen))
+ {
+ double fX(aMinX.solve(rInfoProvider, NumberType::xcoordinate));
+ double fY(aMinY.solve(rInfoProvider, NumberType::ycoordinate));
+ double fW(aWidth.solve(rInfoProvider, NumberType::xcoordinate));
+ double fH(aHeight.solve(rInfoProvider, NumberType::ycoordinate));
+ return basegfx::B2DRange(fX,fY,fX+fW,fY+fH);
+ }
+ }
+ }
+ }
+ }
+
+ return basegfx::B2DRange();
+ }
+
+ std::vector<double> readFilterMatrix(std::u16string_view rCandidate, InfoProvider const & rInfoProvider)
+ {
+ std::vector<double> aVector;
+ const sal_Int32 nLen(rCandidate.size());
+
+ sal_Int32 nPos(0);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ SvgNumber aVal;
+
+ while (nPos < nLen)
+ {
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aVector.push_back(aVal.solve(rInfoProvider));
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ }
+ }
+
+ return aVector;
+ }
+
+ basegfx::B2DHomMatrix readTransform(std::u16string_view rCandidate, InfoProvider const & rInfoProvider)
+ {
+ basegfx::B2DHomMatrix aMatrix;
+ const sal_Int32 nLen(rCandidate.size());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ while(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+ const sal_Int32 nInitPos(nPos);
+ static constexpr std::u16string_view aStrMatrix = u"matrix";
+ static constexpr std::u16string_view aStrTranslate = u"translate";
+ static constexpr std::u16string_view aStrScale = u"scale";
+ static constexpr std::u16string_view aStrRotate = u"rotate";
+ static constexpr std::u16string_view aStrSkewX = u"skewX";
+ static constexpr std::u16string_view aStrSkewY = u"skewY";
+
+ switch(aChar)
+ {
+ case u'm' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrMatrix, nPos))
+ {
+ // matrix element
+ nPos += aStrMatrix.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ SvgNumber aVal;
+ basegfx::B2DHomMatrix aNew;
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(0, 0, aVal.solve(rInfoProvider)); // Element A
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(1, 0, aVal.solve(rInfoProvider)); // Element B
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(0, 1, aVal.solve(rInfoProvider)); // Element C
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(1, 1, aVal.solve(rInfoProvider)); // Element D
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(0, 2, aVal.solve(rInfoProvider, NumberType::xcoordinate)); // Element E
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ if(readNumberAndUnit(rCandidate, nPos, aVal, nLen))
+ {
+ aNew.set(1, 2, aVal.solve(rInfoProvider, NumberType::ycoordinate)); // Element F
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ // caution: String is evaluated from left to right, but matrix multiplication
+ // in SVG is right to left, so put the new transformation before the current
+ // one by multiplicating from the right side
+ aMatrix = aMatrix * aNew;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ case u't' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrTranslate, nPos))
+ {
+ // translate element
+ nPos += aStrTranslate.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ SvgNumber aTransX;
+
+ if(readNumberAndUnit(rCandidate, nPos, aTransX, nLen))
+ {
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ SvgNumber aTransY;
+ readNumberAndUnit(rCandidate, nPos, aTransY, nLen);
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ aMatrix = aMatrix * basegfx::utils::createTranslateB2DHomMatrix(
+ aTransX.solve(rInfoProvider, NumberType::xcoordinate),
+ aTransY.solve(rInfoProvider, NumberType::ycoordinate));
+ }
+ }
+ break;
+ }
+ case u's' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrScale, nPos))
+ {
+ // scale element
+ nPos += aStrScale.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ SvgNumber aScaleX;
+
+ if(readNumberAndUnit(rCandidate, nPos, aScaleX, nLen))
+ {
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ SvgNumber aScaleY(aScaleX);
+ readNumberAndUnit(rCandidate, nPos, aScaleY, nLen);
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ aMatrix = aMatrix * basegfx::utils::createScaleB2DHomMatrix(
+ aScaleX.solve(rInfoProvider),
+ aScaleY.solve(rInfoProvider));
+ }
+ }
+ else if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrSkewX, nPos))
+ {
+ // skewx element
+ nPos += aStrSkewX.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ double fSkewX(0.0);
+
+ if(readAngle(rCandidate, nPos, fSkewX, nLen))
+ {
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ aMatrix = aMatrix * basegfx::utils::createShearXB2DHomMatrix(tan(fSkewX));
+ }
+ }
+ else if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrSkewY, nPos))
+ {
+ // skewy element
+ nPos += aStrSkewY.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ double fSkewY(0.0);
+
+ if(readAngle(rCandidate, nPos, fSkewY, nLen))
+ {
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ aMatrix = aMatrix * basegfx::utils::createShearYB2DHomMatrix(tan(fSkewY));
+ }
+ }
+ break;
+ }
+ case u'r' :
+ {
+ if(o3tl::matchIgnoreAsciiCase(rCandidate, aStrRotate, nPos))
+ {
+ // rotate element
+ nPos += aStrRotate.size();
+ skip_char(rCandidate, ' ', '(', nPos, nLen);
+ double fAngle(0.0);
+
+ if(readAngle(rCandidate, nPos, fAngle, nLen))
+ {
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ SvgNumber aX;
+ readNumberAndUnit(rCandidate, nPos, aX, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ SvgNumber aY;
+ readNumberAndUnit(rCandidate, nPos, aY, nLen);
+ skip_char(rCandidate, ' ', ')', nPos, nLen);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ const double fX(aX.isSet() ? aX.solve(rInfoProvider, NumberType::xcoordinate) : 0.0);
+ const double fY(aY.isSet() ? aY.solve(rInfoProvider, NumberType::ycoordinate) : 0.0);
+
+ if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
+ {
+ // rotate around point
+ aMatrix = aMatrix * basegfx::utils::createRotateAroundPoint(fX, fY, fAngle);
+ }
+ else
+ {
+ // rotate
+ aMatrix = aMatrix * basegfx::utils::createRotateB2DHomMatrix(fAngle);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if(nInitPos == nPos)
+ {
+ SAL_WARN("svgio", "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+ }
+
+ return aMatrix;
+ }
+
+ bool readSingleNumber(std::u16string_view rCandidate, SvgNumber& aNum)
+ {
+ const sal_Int32 nLen(rCandidate.size());
+ sal_Int32 nPos(0);
+
+ return readNumberAndUnit(rCandidate, nPos, aNum, nLen);
+ }
+
+ bool readLocalLink(std::u16string_view rCandidate, OUString& rURL)
+ {
+ sal_Int32 nPos(0);
+ const sal_Int32 nLen(rCandidate.size());
+
+ skip_char(rCandidate, ' ', nPos, nLen);
+
+ if (nLen && nPos < nLen && '#' == rCandidate[nPos])
+ {
+ ++nPos;
+ rURL = rCandidate.substr(nPos);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ bool readLocalUrl(const OUString& rCandidate, OUString& rURL)
+ {
+ static const char aStrUrl[] = "url(";
+
+ if(rCandidate.startsWithIgnoreAsciiCase(aStrUrl))
+ {
+ const sal_Int32 nLen(rCandidate.getLength());
+ sal_Int32 nPos(strlen(aStrUrl));
+ sal_Unicode aLimiter(')');
+
+ skip_char(rCandidate, ' ', nPos, nLen);
+
+ if('"' == rCandidate[nPos])
+ {
+ aLimiter = '"';
+ ++nPos;
+ }
+ else if('\'' == rCandidate[nPos])
+ {
+ aLimiter = '\'';
+ ++nPos;
+ }
+
+ skip_char(rCandidate, ' ', nPos, nLen);
+ skip_char(rCandidate, '#', nPos, nPos + 1);
+ OUStringBuffer aTokenValue;
+
+ copyToLimiter(rCandidate, aLimiter, nPos, aTokenValue, nLen);
+
+ rURL = aTokenValue.makeStringAndClear();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ bool readSvgPaint(const OUString& rCandidate, SvgPaint& rSvgPaint,
+ OUString& rURL, SvgNumber& rOpacity)
+ {
+ if( !rCandidate.isEmpty() )
+ {
+ basegfx::BColor aColor;
+
+ if(read_color(rCandidate, aColor, rOpacity))
+ {
+ rSvgPaint = SvgPaint(aColor, true, true);
+ return true;
+ }
+ else
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate), u"none"))
+ {
+ rSvgPaint = SvgPaint(aColor, true, false, false);
+ return true;
+ }
+ else if(readLocalUrl(rCandidate, rURL))
+ {
+ /// Url is copied to rURL, but needs to be solved outside this helper
+ return false;
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(rCandidate), u"currentColor"))
+ {
+ rSvgPaint = SvgPaint(aColor, true, true, true);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool readSvgNumberVector(std::u16string_view rCandidate, SvgNumberVector& rSvgNumberVector)
+ {
+ const sal_Int32 nLen(rCandidate.size());
+ rSvgNumberVector.clear();
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ SvgNumber aNum;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ while(readNumberAndUnit(rCandidate, nPos, aNum, nLen))
+ {
+ rSvgNumberVector.push_back(aNum);
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+ }
+
+ return !rSvgNumberVector.empty();
+ }
+
+ return false;
+ }
+
+ SvgAspectRatio readSvgAspectRatio(std::u16string_view rCandidate)
+ {
+ const sal_Int32 nLen(rCandidate.size());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ SvgAlign aSvgAlign(SvgAlign::xMidYMid);
+ bool bMeetOrSlice(true);
+ bool bChanged(false);
+
+ while(nPos < nLen)
+ {
+ const sal_Int32 nInitPos(nPos);
+ skip_char(rCandidate, ' ', nPos, nLen);
+ OUStringBuffer aTokenName;
+ copyString(rCandidate, nPos, aTokenName, nLen);
+
+ if(!aTokenName.isEmpty())
+ {
+ switch(StrToSVGToken(aTokenName.makeStringAndClear(), false))
+ {
+ case SVGToken::Defer:
+ {
+ bChanged = true;
+ break;
+ }
+ case SVGToken::None:
+ {
+ aSvgAlign = SvgAlign::none;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMinYMin:
+ {
+ aSvgAlign = SvgAlign::xMinYMin;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMidYMin:
+ {
+ aSvgAlign = SvgAlign::xMidYMin;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMaxYMin:
+ {
+ aSvgAlign = SvgAlign::xMaxYMin;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMinYMid:
+ {
+ aSvgAlign = SvgAlign::xMinYMid;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMidYMid:
+ {
+ aSvgAlign = SvgAlign::xMidYMid;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMaxYMid:
+ {
+ aSvgAlign = SvgAlign::xMaxYMid;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMinYMax:
+ {
+ aSvgAlign = SvgAlign::xMinYMax;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMidYMax:
+ {
+ aSvgAlign = SvgAlign::xMidYMax;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::XMaxYMax:
+ {
+ aSvgAlign = SvgAlign::xMaxYMax;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::Meet:
+ {
+ bMeetOrSlice = true;
+ bChanged = true;
+ break;
+ }
+ case SVGToken::Slice:
+ {
+ bMeetOrSlice = false;
+ bChanged = true;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ if(nInitPos == nPos)
+ {
+ SAL_WARN("svgio", "Could not interpret on current position (!)");
+ nPos++;
+ }
+ }
+
+ if(bChanged)
+ {
+ return SvgAspectRatio(aSvgAlign, bMeetOrSlice);
+ }
+ }
+
+ return SvgAspectRatio();
+ }
+
+ bool readSvgStringVector(std::u16string_view rCandidate, SvgStringVector& rSvgStringVector)
+ {
+ rSvgStringVector.clear();
+ const sal_Int32 nLen(rCandidate.size());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ OUStringBuffer aTokenValue;
+ skip_char(rCandidate, ' ', ',', nPos, nLen);
+
+ while(nPos < nLen)
+ {
+ copyToLimiter(rCandidate, ',', nPos, aTokenValue, nLen);
+ skip_char(rCandidate, ',', ' ', nPos, nLen);
+ const OUString aString = aTokenValue.makeStringAndClear();
+
+ if(!aString.isEmpty())
+ {
+ rSvgStringVector.push_back(aString);
+ }
+ }
+ }
+
+ return !rSvgStringVector.empty();
+ }
+
+ void readImageLink(const OUString& rCandidate, OUString& rXLink, OUString& rUrl, OUString& rData)
+ {
+ rXLink.clear();
+ rUrl.clear();
+ rData.clear();
+
+ if(!readLocalLink(rCandidate, rXLink))
+ {
+ static const char aStrData[] = "data:";
+
+ if(rCandidate.matchIgnoreAsciiCase(aStrData, 0))
+ {
+ // embedded data
+ sal_Int32 nPos(strlen(aStrData));
+ sal_Int32 nLen(rCandidate.getLength());
+ OUStringBuffer aBuffer;
+
+ // read mime type
+ skip_char(rCandidate, ' ', nPos, nLen);
+ copyToLimiter(rCandidate, ';', nPos, aBuffer, nLen);
+ skip_char(rCandidate, ' ', ';', nPos, nLen);
+ const OUString aMimeType = aBuffer.makeStringAndClear();
+
+ if(!aMimeType.isEmpty() && nPos < nLen)
+ {
+ if(aMimeType.startsWith("image"))
+ {
+ // image data
+ std::u16string_view aData(rCandidate.subView(nPos));
+ static constexpr std::u16string_view aStrBase64 = u"base64";
+
+ if(o3tl::starts_with(aData, aStrBase64))
+ {
+ // base64 encoded
+ nPos = aStrBase64.size();
+ nLen = aData.size();
+
+ skip_char(aData, ' ', ',', nPos, nLen);
+
+ if(nPos < nLen)
+ {
+ rData = aData.substr(nPos);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // Url (path and filename)
+ rUrl = rCandidate;
+ }
+ }
+ }
+
+ // #i125325#
+ OUString removeBlockComments(const OUString& rCandidate)
+ {
+ const sal_Int32 nLen(rCandidate.getLength());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ OUStringBuffer aBuffer;
+ bool bChanged(false);
+ sal_Int32 nInsideComment(0);
+ const sal_Unicode aCommentSlash('/');
+ const sal_Unicode aCommentStar('*');
+
+ while(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+ const bool bStart(aCommentSlash == aChar && nPos + 1 < nLen && aCommentStar == rCandidate[nPos + 1]);
+ const bool bEnd(aCommentStar == aChar && nPos + 1 < nLen && aCommentSlash == rCandidate[nPos + 1]);
+
+ if(bStart)
+ {
+ nPos += 2;
+ nInsideComment++;
+ bChanged = true;
+ }
+ else if(bEnd)
+ {
+ nPos += 2;
+ nInsideComment--;
+ }
+ else
+ {
+ if(!nInsideComment)
+ {
+ aBuffer.append(aChar);
+ }
+
+ nPos++;
+ }
+ }
+
+ if(bChanged)
+ {
+ return aBuffer.makeStringAndClear();
+ }
+ }
+
+ return rCandidate;
+ }
+
+ OUString consolidateContiguousSpace(const OUString& rCandidate)
+ {
+ const sal_Int32 nLen(rCandidate.getLength());
+
+ if(nLen)
+ {
+ sal_Int32 nPos(0);
+ OUStringBuffer aBuffer;
+ bool bInsideSpace(false);
+ const sal_Unicode aSpace(' ');
+
+ while(nPos < nLen)
+ {
+ const sal_Unicode aChar(rCandidate[nPos]);
+
+ if(aSpace == aChar)
+ {
+ bInsideSpace = true;
+ }
+ else
+ {
+ if(bInsideSpace)
+ {
+ bInsideSpace = false;
+ aBuffer.append(aSpace);
+ }
+
+ aBuffer.append(aChar);
+ }
+
+ nPos++;
+ }
+
+ if(bInsideSpace)
+ {
+ aBuffer.append(aSpace);
+ }
+
+ if(aBuffer.getLength() != nLen)
+ {
+ return aBuffer.makeStringAndClear();
+ }
+ }
+
+ return rCandidate;
+ }
+
+ ::std::vector< double > solveSvgNumberVector(const SvgNumberVector& rInput, const InfoProvider& rInfoProvider)
+ {
+ ::std::vector< double > aRetval;
+
+ if(!rInput.empty())
+ {
+ const double nCount(rInput.size());
+ aRetval.reserve(nCount);
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ aRetval.push_back(rInput[a].solve(rInfoProvider));
+ }
+ }
+
+ return aRetval;
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtrefnode.cxx b/svgio/source/svgreader/svgtrefnode.cxx
new file mode 100644
index 0000000000..eb59acfec9
--- /dev/null
+++ b/svgio/source/svgreader/svgtrefnode.cxx
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtrefnode.hxx>
+#include <svgdocument.hxx>
+
+namespace svgio::svgreader
+{
+ SvgTrefNode::SvgTrefNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Tref, rDocument, pParent),
+ maSvgStyleAttributes(*this)
+ {
+ }
+
+ SvgTrefNode::~SvgTrefNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgTrefNode::getSvgStyleAttributes() const
+ {
+ return &maSvgStyleAttributes;
+ }
+
+ void SvgTrefNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ readLocalLink(aContent, maXLink);
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ const SvgTextNode* SvgTrefNode::getReferencedSvgTextNode() const
+ {
+ return dynamic_cast< const SvgTextNode* >(getDocument().findSvgNodeById(maXLink));
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtspannode.cxx b/svgio/source/svgreader/svgtspannode.cxx
new file mode 100644
index 0000000000..27d714e66a
--- /dev/null
+++ b/svgio/source/svgreader/svgtspannode.cxx
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgtspannode.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace svgio::svgreader
+{
+ SvgTspanNode::SvgTspanNode(
+ SVGToken aType,
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(aType, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ mbLengthAdjust(true),
+ mnTextLineWidth(0.0)
+ {
+ }
+
+ SvgTspanNode::~SvgTspanNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgTspanNode::getSvgStyleAttributes() const
+ {
+ // #i125293# Need to support CssStyles in tspan text sections
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgTspanNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumberVector aVector;
+
+ if(readSvgNumberVector(aContent, aVector))
+ {
+ setX(std::move(aVector));
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumberVector aVector;
+
+ if(readSvgNumberVector(aContent, aVector))
+ {
+ setY(std::move(aVector));
+ }
+ break;
+ }
+ case SVGToken::Dx:
+ {
+ SvgNumberVector aVector;
+
+ if(readSvgNumberVector(aContent, aVector))
+ {
+ setDx(std::move(aVector));
+ }
+ break;
+ }
+ case SVGToken::Dy:
+ {
+ SvgNumberVector aVector;
+
+ if(readSvgNumberVector(aContent, aVector))
+ {
+ setDy(std::move(aVector));
+ }
+ break;
+ }
+ case SVGToken::Rotate:
+ {
+ SvgNumberVector aVector;
+
+ if(readSvgNumberVector(aContent, aVector))
+ {
+ setRotate(std::move(aVector));
+ }
+ break;
+ }
+ case SVGToken::TextLength:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ setTextLength(aNum);
+ }
+ }
+ break;
+ }
+ case SVGToken::LengthAdjust:
+ {
+ if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacing"))
+ {
+ setLengthAdjust(true);
+ }
+ else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"spacingAndGlyphs"))
+ {
+ setLengthAdjust(false);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ double SvgTspanNode::getCurrentFontSize() const
+ {
+ return getCurrentFontSizeInherited();
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgusenode.cxx b/svgio/source/svgreader/svgusenode.cxx
new file mode 100644
index 0000000000..d34e94427e
--- /dev/null
+++ b/svgio/source/svgreader/svgusenode.cxx
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <svgusenode.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <svgdocument.hxx>
+
+namespace svgio::svgreader
+{
+ SvgUseNode::SvgUseNode(
+ SvgDocument& rDocument,
+ SvgNode* pParent)
+ : SvgNode(SVGToken::Use, rDocument, pParent),
+ maSvgStyleAttributes(*this),
+ mbDecomposingSvgNode(false)
+ {
+ }
+
+ SvgUseNode::~SvgUseNode()
+ {
+ }
+
+ const SvgStyleAttributes* SvgUseNode::getSvgStyleAttributes() const
+ {
+ return checkForCssStyle(maSvgStyleAttributes);
+ }
+
+ void SvgUseNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
+ {
+ // call parent
+ SvgNode::parseAttribute(aSVGToken, aContent);
+
+ // read style attributes
+ maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+ // parse own
+ switch(aSVGToken)
+ {
+ case SVGToken::Style:
+ {
+ readLocalCssStyle(aContent);
+ break;
+ }
+ case SVGToken::Transform:
+ {
+ const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
+
+ if(!aMatrix.isIdentity())
+ {
+ setTransform(aMatrix);
+ }
+ break;
+ }
+ case SVGToken::X:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maX = aNum;
+ }
+ break;
+ }
+ case SVGToken::Y:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ maY = aNum;
+ }
+ break;
+ }
+ case SVGToken::Width:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maWidth = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Height:
+ {
+ SvgNumber aNum;
+
+ if(readSingleNumber(aContent, aNum))
+ {
+ if(aNum.isPositive())
+ {
+ maHeight = aNum;
+ }
+ }
+ break;
+ }
+ case SVGToken::Href:
+ case SVGToken::XlinkHref:
+ {
+ readLocalLink(aContent, maXLink);
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ void SvgUseNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
+ basegfx::B2DHomMatrix aTransform;
+
+ // try to access link to content
+ const SvgNode* pXLink = getDocument().findSvgNodeById(maXLink);
+
+ if (pXLink)
+ {
+ if (Display::None == pXLink->getDisplay() || mbDecomposingSvgNode)
+ return;
+
+ // todo: in case mpXLink is a SVGToken::Svg or SVGToken::Symbol the
+ // SVG docs want the getWidth() and getHeight() from this node
+ // to be valid for the subtree.
+ mbDecomposingSvgNode = true;
+ const_cast< SvgNode* >(pXLink)->setAlternativeParent(this);
+ pXLink->decomposeSvgNode(aNewTarget, true);
+ const_cast< SvgNode* >(pXLink)->setAlternativeParent();
+ mbDecomposingSvgNode = false;
+
+ if(aNewTarget.empty())
+ return;
+
+ if(getX().isSet() || getY().isSet())
+ {
+ aTransform.translate(
+ getX().solve(*this, NumberType::xcoordinate),
+ getY().solve(*this, NumberType::ycoordinate));
+ }
+
+ if(getTransform())
+ {
+ aTransform = *getTransform() * aTransform;
+ }
+ }
+
+ const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+ if(pStyle)
+ {
+ if(Display::None != getDisplay())
+ {
+ pStyle->add_postProcess(rTarget, std::move(aNewTarget), aTransform);
+ }
+ }
+ }
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgvisitor.cxx b/svgio/source/svgreader/svgvisitor.cxx
new file mode 100644
index 0000000000..8ad5395f1c
--- /dev/null
+++ b/svgio/source/svgreader/svgvisitor.cxx
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+
+#include <svgrectnode.hxx>
+#include <svgsvgnode.hxx>
+#include <svgpathnode.hxx>
+#include <svggradientnode.hxx>
+
+#include <svgvisitor.hxx>
+
+namespace svgio::svgreader
+{
+SvgDrawVisitor::SvgDrawVisitor()
+ : mpDrawRoot(std::make_shared<gfx::DrawRoot>())
+ , mpCurrent(mpDrawRoot)
+{
+}
+
+void SvgDrawVisitor::visit(svgio::svgreader::SvgNode const& rNode)
+{
+ switch (rNode.getType())
+ {
+ case svgio::svgreader::SVGToken::Svg:
+ {
+ auto const& rSvgNode = static_cast<svgio::svgreader::SvgSvgNode const&>(rNode);
+
+ basegfx::B2DRange aRange = rSvgNode.getCurrentViewPort();
+
+ static_cast<gfx::DrawRoot*>(mpCurrent.get())->maRectangle = aRange;
+ }
+ break;
+ case svgio::svgreader::SVGToken::Rect:
+ {
+ auto const& rRectNode = static_cast<svgio::svgreader::SvgRectNode const&>(rNode);
+
+ double x = rRectNode.getX().getNumber();
+ double y = rRectNode.getY().getNumber();
+ double w = rRectNode.getWidth().getNumber();
+ double h = rRectNode.getHeight().getNumber();
+
+ auto pRectangle
+ = std::make_shared<gfx::DrawRectangle>(basegfx::B2DRange(x, y, x + w, y + h));
+ pRectangle->mnRx = rRectNode.getRx().getNumber();
+ pRectangle->mnRy = rRectNode.getRy().getNumber();
+
+ pRectangle->mnStrokeWidth
+ = rRectNode.getSvgStyleAttributes()->getStrokeWidth().getNumber();
+
+ pRectangle->mnOpacity = rRectNode.getSvgStyleAttributes()->getOpacity().getNumber();
+
+ const basegfx::BColor* pFillColor = rRectNode.getSvgStyleAttributes()->getFill();
+ const SvgGradientNode* pFillGradient
+ = rRectNode.getSvgStyleAttributes()->getSvgGradientNodeFill();
+ if (pFillColor)
+ {
+ pRectangle->mpFillColor = std::make_shared<basegfx::BColor>(*pFillColor);
+ }
+ else if (pFillGradient)
+ {
+ drawinglayer::primitive2d::SvgGradientEntryVector aSvgGradientEntryVector;
+ pFillGradient->collectGradientEntries(aSvgGradientEntryVector);
+ if (!aSvgGradientEntryVector.empty())
+ {
+ auto aGradientInfo = std::make_shared<gfx::LinearGradientInfo>();
+
+ aGradientInfo->x1 = pFillGradient->getX1().getNumber();
+ aGradientInfo->y1 = pFillGradient->getY1().getNumber();
+ aGradientInfo->x2 = pFillGradient->getX2().getNumber();
+ aGradientInfo->y2 = pFillGradient->getY2().getNumber();
+
+ std::optional<basegfx::B2DHomMatrix> pGradientTransform
+ = pFillGradient->getGradientTransform();
+ if (pGradientTransform)
+ {
+ aGradientInfo->maMatrix = *pGradientTransform;
+ }
+
+ pRectangle->mpFillGradient = aGradientInfo;
+
+ for (auto const& rEntry : aSvgGradientEntryVector)
+ {
+ gfx::GradientStop aStop;
+ aStop.maColor = rEntry.getColor();
+ aStop.mfOffset = rEntry.getOffset();
+ aStop.mfOpacity = rEntry.getOpacity();
+ pRectangle->mpFillGradient->maGradientStops.push_back(aStop);
+ }
+ }
+ }
+
+ const basegfx::BColor* pStrokeColor = rRectNode.getSvgStyleAttributes()->getStroke();
+ if (pStrokeColor)
+ pRectangle->mpStrokeColor = std::make_shared<basegfx::BColor>(*pStrokeColor);
+
+ mpCurrent->maChildren.push_back(pRectangle);
+ }
+ break;
+ case svgio::svgreader::SVGToken::Path:
+ {
+ auto const& rPathNode = static_cast<svgio::svgreader::SvgPathNode const&>(rNode);
+
+ auto pPath = rPathNode.getPath();
+ if (pPath)
+ {
+ auto pDrawPath = std::make_shared<gfx::DrawPath>(*pPath);
+
+ pDrawPath->mnStrokeWidth
+ = rPathNode.getSvgStyleAttributes()->getStrokeWidth().getNumber();
+
+ pDrawPath->mnOpacity = rPathNode.getSvgStyleAttributes()->getOpacity().getNumber();
+
+ const basegfx::BColor* pFillColor = rPathNode.getSvgStyleAttributes()->getFill();
+ if (pFillColor)
+ pDrawPath->mpFillColor = std::make_shared<basegfx::BColor>(*pFillColor);
+
+ const basegfx::BColor* pStrokeColor
+ = rPathNode.getSvgStyleAttributes()->getStroke();
+ if (pStrokeColor)
+ pDrawPath->mpStrokeColor = std::make_shared<basegfx::BColor>(*pStrokeColor);
+
+ mpCurrent->maChildren.push_back(pDrawPath);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ goToChildren(rNode);
+}
+
+void SvgDrawVisitor::goToChildren(svgio::svgreader::SvgNode const& rNode)
+{
+ for (auto& rChild : rNode.getChildren())
+ {
+ rChild->accept(*this);
+ }
+}
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svguno/xsvgparser.cxx b/svgio/source/svguno/xsvgparser.cxx
new file mode 100644
index 0000000000..8c216821b4
--- /dev/null
+++ b/svgio/source/svguno/xsvgparser.cxx
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/graphic/XSvgParser.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/xml/sax/XParser.hpp>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <com/sun/star/xml/sax/InputSource.hpp>
+#include <svgdocumenthandler.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <rtl/ref.hxx>
+#include <tools/stream.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <svgvisitor.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace svgio::svgreader
+{
+ namespace {
+
+ class XSvgParser : public ::cppu::WeakImplHelper< graphic::XSvgParser, lang::XServiceInfo >
+ {
+ private:
+ std::shared_ptr<SvgDrawVisitor> mpVisitor;
+
+ uno::Reference< uno::XComponentContext > context_;
+ bool parseSvgXML(uno::Reference<io::XInputStream> const & xSVGStream,
+ uno::Reference<xml::sax::XDocumentHandler> const & xSvgDocHdl);
+ public:
+ explicit XSvgParser(
+ uno::Reference< uno::XComponentContext > context);
+ XSvgParser(const XSvgParser&) = delete;
+ XSvgParser& operator=(const XSvgParser&) = delete;
+
+ // XSvgParser
+ virtual uno::Sequence< uno::Reference< ::graphic::XPrimitive2D > > SAL_CALL getDecomposition(
+ const uno::Reference< ::io::XInputStream >& xSVGStream,
+ const OUString& aAbsolutePath) override;
+
+ virtual uno::Any SAL_CALL getDrawCommands(
+ uno::Reference<io::XInputStream> const & xSvgStream,
+ const OUString& aAbsolutePath) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString&) override;
+ virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+ };
+
+ }
+
+ XSvgParser::XSvgParser(
+ uno::Reference< uno::XComponentContext > context):
+ context_(std::move(context))
+ {
+ }
+
+ bool XSvgParser::parseSvgXML(uno::Reference<io::XInputStream> const & xSVGStream, uno::Reference<xml::sax::XDocumentHandler> const & xSvgDocHdl)
+ {
+ try
+ {
+ // prepare ParserInputSource
+ xml::sax::InputSource myInputSource;
+ myInputSource.aInputStream = xSVGStream;
+
+ // get parser
+ uno::Reference< xml::sax::XParser > xParser(
+ xml::sax::Parser::create(context_));
+ // fdo#60471 need to enable internal entities because
+ // certain ... popular proprietary products write SVG files
+ // that use entities to define XML namespaces.
+ uno::Reference<lang::XInitialization> const xInit(xParser,
+ uno::UNO_QUERY_THROW);
+ uno::Sequence<uno::Any> args{ uno::Any(OUString("DoSmeplease")) };
+ xInit->initialize(args);
+
+ // connect parser and filter
+ xParser->setDocumentHandler(xSvgDocHdl);
+
+ // finally, parse the stream to a hierarchy of
+ // SVGGraphicPrimitive2D which will be embedded to the
+ // primitive sequence. Their decompositions will in the
+ // end create local low-level primitives, thus SVG will
+ // be processable from all our processors
+ xParser->parseStream(myInputSource);
+ }
+ catch(const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION( "svg", "Parse error");
+ return false;
+ }
+
+ return true;
+ }
+
+ uno::Sequence< uno::Reference< ::graphic::XPrimitive2D > > XSvgParser::getDecomposition(
+ const uno::Reference< ::io::XInputStream >& xSVGStream,
+ const OUString& aAbsolutePath )
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aRetval;
+
+ if(xSVGStream.is())
+ {
+ // local document handler
+ rtl::Reference<SvgDocHdl> pSvgDocHdl = new SvgDocHdl(aAbsolutePath);
+ parseSvgXML(xSVGStream, pSvgDocHdl);
+
+ // decompose to primitives
+ for(std::unique_ptr<SvgNode> const & pCandidate : pSvgDocHdl->getSvgDocument().getSvgNodeVector())
+ {
+ if (Display::None != pCandidate->getDisplay())
+ {
+ pCandidate->decomposeSvgNode(aRetval, false);
+ }
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Invalid stream (!)");
+ }
+
+ return aRetval.toSequence();
+ }
+
+ uno::Any SAL_CALL XSvgParser::getDrawCommands(
+ uno::Reference<io::XInputStream> const & xSvgStream,
+ const OUString& aAbsolutePath)
+ {
+ uno::Any aAnyResult;
+
+ if (!xSvgStream.is())
+ return aAnyResult;
+
+ rtl::Reference<SvgDocHdl> pSvgDocHdl = new SvgDocHdl(aAbsolutePath);
+ parseSvgXML(xSvgStream, pSvgDocHdl);
+
+ // decompose to primitives
+ for (std::unique_ptr<SvgNode> const & pCandidate : pSvgDocHdl->getSvgDocument().getSvgNodeVector())
+ {
+ if (Display::None != pCandidate->getDisplay())
+ {
+ mpVisitor = std::make_shared<SvgDrawVisitor>();
+ pCandidate->accept(*mpVisitor);
+ std::shared_ptr<gfx::DrawRoot> pDrawRoot(mpVisitor->getDrawRoot());
+ sal_uInt64 nPointer = reinterpret_cast<sal_uInt64>(pDrawRoot.get());
+ aAnyResult <<= sal_uInt64(nPointer);
+ }
+ }
+
+ return aAnyResult;
+ }
+
+ OUString SAL_CALL XSvgParser::getImplementationName()
+ {
+ return "svgio::svgreader::XSvgParser";
+ }
+
+ sal_Bool SAL_CALL XSvgParser::supportsService(const OUString& rServiceName)
+ {
+ return cppu::supportsService(this, rServiceName);
+ }
+
+ uno::Sequence< OUString > SAL_CALL XSvgParser::getSupportedServiceNames()
+ {
+ return { "com.sun.star.graphic.SvgTools" };
+ }
+
+} // end of namespace svgio::svgreader
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+svgio_XSvgParser_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new svgio::svgreader::XSvgParser(context));
+}
+
+extern "C" bool TestImportSVG(SvStream& rStream)
+{
+ css::uno::Reference<css::io::XInputStream> xStream(new utl::OInputStreamWrapper(rStream));
+ rtl::Reference<svgio::svgreader::XSvgParser> xSvgParser(new svgio::svgreader::XSvgParser(comphelper::getProcessComponentContext()));
+ return xSvgParser->getDecomposition(xStream, OUString()).getLength() != 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/svgio.component b/svgio/svgio.component
new file mode 100644
index 0000000000..cd4e407c7a
--- /dev/null
+++ b/svgio/svgio.component
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="svgio::svgreader::XSvgParser"
+ constructor="svgio_XSvgParser_get_implementation">
+ <service name="com.sun.star.graphic.SvgTools"/>
+ </implementation>
+</component>