diff options
Diffstat (limited to 'svgio')
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:"DejaVu Sans"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> |