浏览代码

Allow SVG XML data to be passed as inline RML (#777)

mcukstorm 1 月之前
父节点
当前提交
9684050cc1

+ 5 - 1
Include/RmlUi/Core/BaseXMLParser.h

@@ -39,7 +39,7 @@ class Stream;
 class URL;
 class URL;
 using XMLAttributes = Dictionary;
 using XMLAttributes = Dictionary;
 
 
-enum class XMLDataType { Text, CData, InnerXML };
+enum class XMLDataType { Text, CDATA, InnerXML };
 
 
 /**
 /**
     @author Peter Curry
     @author Peter Curry
@@ -55,6 +55,10 @@ public:
 	/// @param[in] tag The tag to register as containing generic character data.
 	/// @param[in] tag The tag to register as containing generic character data.
 	void RegisterCDATATag(const String& tag);
 	void RegisterCDATATag(const String& tag);
 
 
+	/// Checks if the given tag is registered as a CDATA tag.
+	/// @param[in] tag The tag to check if it is registered as containing generic character data
+	bool IsCDATATag(const String& tag);
+
 	/// When an XML attribute with the given name is encountered during parsing, then all content below the current
 	/// When an XML attribute with the given name is encountered during parsing, then all content below the current
 	/// node is treated as data.
 	/// node is treated as data.
 	/// @note While children nodes are treated as data (text), it is assumed that the content represents valid XML.
 	/// @note While children nodes are treated as data (text), it is assumed that the content represents valid XML.

+ 1 - 1
Include/RmlUi/Core/Element.h

@@ -473,7 +473,7 @@ public:
 	String GetInnerRML() const;
 	String GetInnerRML() const;
 	/// Sets the markup and content of the element. All existing children will be replaced.
 	/// Sets the markup and content of the element. All existing children will be replaced.
 	/// @param[in] rml The new content of the element.
 	/// @param[in] rml The new content of the element.
-	void SetInnerRML(const String& rml);
+	virtual void SetInnerRML(const String& rml);
 
 
 	//@}
 	//@}
 
 

+ 10 - 2
Include/RmlUi/Core/XMLParser.h

@@ -51,6 +51,13 @@ public:
 	XMLParser(Element* root);
 	XMLParser(Element* root);
 	~XMLParser();
 	~XMLParser();
 
 
+	/// Registers a tag were its contents should be treated as CDATA.
+	///		Whereas BaseXMLParser RegisterCDataTag only registeres a
+	///		tag for a parser instance, this function willl register
+	///		a tag for all XMLParser instances created after this call.
+	/// @param[in] _tag The tag for contents to be treated as CDATA
+	static void RegisterPersistentCDATATag(const String& _tag);
+
 	/// Registers a custom node handler to be used to a given tag.
 	/// Registers a custom node handler to be used to a given tag.
 	/// @param[in] tag The tag the custom parser will handle.
 	/// @param[in] tag The tag the custom parser will handle.
 	/// @param[in] handler The custom handler.
 	/// @param[in] handler The custom handler.
@@ -95,13 +102,14 @@ public:
 	/// Returns the source URL of this parse.
 	/// Returns the source URL of this parse.
 	const URL& GetSourceURL() const;
 	const URL& GetSourceURL() const;
 
 
+	/// Called when the parser encounters data.
+	void HandleData(const String& data, XMLDataType type) override;
+
 protected:
 protected:
 	/// Called when the parser finds the beginning of an element tag.
 	/// Called when the parser finds the beginning of an element tag.
 	void HandleElementStart(const String& name, const XMLAttributes& attributes) override;
 	void HandleElementStart(const String& name, const XMLAttributes& attributes) override;
 	/// Called when the parser finds the end of an element tag.
 	/// Called when the parser finds the end of an element tag.
 	void HandleElementEnd(const String& name) override;
 	void HandleElementEnd(const String& name) override;
-	/// Called when the parser encounters data.
-	void HandleData(const String& data, XMLDataType type) override;
 
 
 private:
 private:
 	UniquePtr<DocumentHeader> header;
 	UniquePtr<DocumentHeader> header;

+ 13 - 4
Include/RmlUi/SVG/ElementSVG.h

@@ -41,8 +41,8 @@ class RMLUICORE_API ElementSVG : public Element {
 public:
 public:
 	RMLUI_RTTI_DefineWithParent(ElementSVG, Element)
 	RMLUI_RTTI_DefineWithParent(ElementSVG, Element)
 
 
-	ElementSVG(const String& tag);
-	virtual ~ElementSVG();
+	explicit ElementSVG(const String& tag);
+	~ElementSVG() override;
 
 
 	/// Returns the element's inherent size.
 	/// Returns the element's inherent size.
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
@@ -50,6 +50,14 @@ public:
 	/// Loads the current source file if needed. This normally happens automatically during layouting.
 	/// Loads the current source file if needed. This normally happens automatically during layouting.
 	void EnsureSourceLoaded();
 	void EnsureSourceLoaded();
 
 
+	/// Gets the SVG XML data (as text) if using inline SVG, if using a file source this will return a blank string
+	/// @param[out] content The SVG XML data (as text) or blank string
+	void GetInnerRML(String& content) const override;
+
+	/// Gets the SVG XML data (as text) if using inline SVG, if using a file source this will return a blank string
+	/// @param[in] content The SVG XML data (as text) or blank string
+	void SetInnerRML(const String& rml) override;
+
 protected:
 protected:
 	/// Renders the image.
 	/// Renders the image.
 	void OnRender() override;
 	void OnRender() override;
@@ -66,13 +74,14 @@ protected:
 	void OnPropertyChange(const PropertyIdSet& changed_properties) override;
 	void OnPropertyChange(const PropertyIdSet& changed_properties) override;
 
 
 private:
 private:
-	void UpdateCachedData();
+	/// Generate unique internal ids for SVG elements using inline SVG.
+	static unsigned long internal_id_counter;
 
 
+	String svg_data;
 	bool svg_dirty = false;
 	bool svg_dirty = false;
 
 
 	SharedPtr<SVG::SVGData> handle;
 	SharedPtr<SVG::SVGData> handle;
 };
 };
-
 } // namespace Rml
 } // namespace Rml
 
 
 #endif
 #endif

+ 2 - 1
Samples/basic/svg/data/svg_decorator.rml

@@ -1,6 +1,6 @@
 <rml>
 <rml>
 	<head>
 	<head>
-		<title>Demo</title>
+		<title>SVG Decorator</title>
 		<link type="text/template" href="/assets/window.rml" />
 		<link type="text/template" href="/assets/window.rml" />
 		<style>
 		<style>
 			body {
 			body {
@@ -8,6 +8,7 @@
 				height: 225px;
 				height: 225px;
 				margin: auto;
 				margin: auto;
 				left: 400px;
 				left: 400px;
+				top: -400px;
 			}
 			}
 
 
 			div.tiger {
 			div.tiger {

+ 2 - 1
Samples/basic/svg/data/svg_element.rml

@@ -1,6 +1,6 @@
 <rml>
 <rml>
 	<head>
 	<head>
-		<title>Demo</title>
+		<title>SVG Element</title>
 		<link type="text/template" href="/assets/window.rml" />
 		<link type="text/template" href="/assets/window.rml" />
 		<style>
 		<style>
 			body {
 			body {
@@ -8,6 +8,7 @@
 				height: 225px;
 				height: 225px;
 				margin: auto;
 				margin: auto;
 				left: -400px;
 				left: -400px;
+				top: -400px;
 			}
 			}
 
 
 			svg {
 			svg {

+ 32 - 0
Samples/basic/svg/data/svg_inline.rml

@@ -0,0 +1,32 @@
+<rml>
+	<head>
+		<title>SVG Inline</title>
+		<link type="text/template" href="/assets/window.rml" />
+		<style>
+			body {
+				width: 300px;
+				height: 225px;
+				margin: auto;
+				left: -400px;
+				top: 200px;
+			}
+
+			svg {
+				display: inline;
+			}
+
+			button {
+			    display: block;
+			    margin: 0 auto;
+			}
+		</style>
+	</head>
+	<body template="window" data-model="svg_test_model">
+		<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" id="svg_1">
+		    <circle cx="25" cy="25" r="20" stroke="black" stroke-width="3" fill="red" />
+        </svg>
+		<svg width="50" height="50" data-rml="svg_data"></svg>
+		<svg width="50" height="50" data-rml="'<circle cx=&quot;25&quot; cy=&quot;25&quot; r=&quot;20&quot; stroke=&quot;' + line_color + '&quot; stroke-width=&quot;3&quot; fill=&quot;' + fill_color + '&quot; />'"></svg>
+        <button data-event-click="toggle_svg">Click Me</button>
+	</body>
+</rml>

+ 48 - 3
Samples/basic/svg/src/main.cpp

@@ -31,6 +31,34 @@
 #include <RmlUi_Backend.h>
 #include <RmlUi_Backend.h>
 #include <Shell.h>
 #include <Shell.h>
 
 
+struct SVGToogleStruct {
+	Rml::String svg_data = R"(<circle cx="25" cy="25" r="20" stroke="black" stroke-width="3" fill="red" />)";
+	Rml::String line_color = "black";
+	Rml::String fill_color = "red";
+	Rml::Element* svg_element;
+	bool toggle_state = false;
+	void Toggle(Rml::DataModelHandle model, Rml::Event& /*event*/, const Rml::VariantList& /*args*/)
+	{
+		toggle_state = !toggle_state;
+
+		// This example uses 3 methods of setting inline SVG data
+		// - Used to set the svg data via data-rml with the main svg data in the data-rml attribute but concatenating colour variables from the model
+		line_color = toggle_state ? "yellow" : "black";
+		fill_color = toggle_state ? "green" : "red";
+
+		// - Used to set the svg data via data-rml with all the svg data contained within a model property
+		svg_data = R"(<circle cx="25" cy="25" r="20" stroke=")" + line_color + R"(" stroke-width="3" fill=")" + fill_color + R"(" />)";
+
+		// - Using SetInnerRML directly on an SVG element to change the SVG data
+		if (svg_element)
+			svg_element->SetInnerRML(svg_data);
+
+		model.DirtyVariable("svg_data");
+		model.DirtyVariable("line_color");
+		model.DirtyVariable("fill_color");
+	}
+} toggle_model;
+
 #if defined RMLUI_PLATFORM_WIN32
 #if defined RMLUI_PLATFORM_WIN32
 	#include <RmlUi_Include_Windows.h>
 	#include <RmlUi_Include_Windows.h>
 int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
 int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
@@ -69,16 +97,33 @@ int main(int /*argc*/, char** /*argv*/)
 		return -1;
 		return -1;
 	}
 	}
 
 
+	Rml::String svg_data;
+	Rml::DataModelConstructor dm_con = context->CreateDataModel("svg_test_model");
+	if (auto model_handle = dm_con.RegisterStruct<SVGToogleStruct>())
+	{
+		model_handle.RegisterMember("name", &SVGToogleStruct::svg_data);
+		model_handle.RegisterMember("line_color", &SVGToogleStruct::line_color);
+		model_handle.RegisterMember("fill_color", &SVGToogleStruct::fill_color);
+		model_handle.RegisterMember("sprite", &SVGToogleStruct::toggle_state);
+	}
+	dm_con.Bind("svg_data", &toggle_model.svg_data);
+	dm_con.Bind("line_color", &toggle_model.line_color);
+	dm_con.Bind("fill_color", &toggle_model.fill_color);
+	dm_con.BindEventCallback("toggle_svg", &SVGToogleStruct::Toggle, &toggle_model);
+
 	Rml::Debugger::Initialise(context);
 	Rml::Debugger::Initialise(context);
 	Shell::LoadFonts();
 	Shell::LoadFonts();
 
 
 	// Load and show the documents.
 	// Load and show the documents.
-	for (const char* filename : {"basic/svg/data/svg_element.rml", "basic/svg/data/svg_decorator.rml"})
+	std::vector<std::string> rml_docs = {"basic/svg/data/svg_element.rml", "basic/svg/data/svg_decorator.rml", "basic/svg/data/svg_inline.rml"};
+	for (const auto& rml_doc : rml_docs)
 	{
 	{
-		if (Rml::ElementDocument* document = context->LoadDocument(filename))
+		if (Rml::ElementDocument* document = context->LoadDocument(rml_doc))
 		{
 		{
 			document->Show();
 			document->Show();
-			document->GetElementById("title")->SetInnerRML("SVG");
+			document->GetElementById("title")->SetInnerRML(document->GetTitle());
+			if (Rml::Element* svg_element = document->GetElementById("svg_1"))
+				toggle_model.svg_element = svg_element;
 		}
 		}
 	}
 	}
 
 

+ 8 - 6
Source/Core/BaseXMLParser.cpp

@@ -44,6 +44,11 @@ void BaseXMLParser::RegisterCDATATag(const String& tag)
 		cdata_tags.insert(StringUtilities::ToLower(tag));
 		cdata_tags.insert(StringUtilities::ToLower(tag));
 }
 }
 
 
+bool BaseXMLParser::IsCDATATag(const String& tag)
+{
+	return cdata_tags.find(StringUtilities::ToLower(tag)) != cdata_tags.end();
+}
+
 void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name)
 void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name)
 {
 {
 	attributes_for_inner_xml_data.insert(attribute_name);
 	attributes_for_inner_xml_data.insert(attribute_name);
@@ -267,17 +272,14 @@ bool BaseXMLParser::ReadOpenTag()
 	// Check if this tag needs to be processed as CDATA.
 	// Check if this tag needs to be processed as CDATA.
 	if (section_opened)
 	if (section_opened)
 	{
 	{
-		const String lcase_tag_name = StringUtilities::ToLower(tag_name);
-		bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end());
-
-		if (is_cdata_tag)
+		if (IsCDATATag(tag_name))
 		{
 		{
-			if (ReadCDATA(lcase_tag_name.c_str()))
+			if (ReadCDATA(StringUtilities::ToLower(tag_name).c_str()))
 			{
 			{
 				open_tag_depth--;
 				open_tag_depth--;
 				if (!data.empty())
 				if (!data.empty())
 				{
 				{
-					HandleDataInternal(data, XMLDataType::CData);
+					HandleDataInternal(data, XMLDataType::CDATA);
 					data.clear();
 					data.clear();
 				}
 				}
 				HandleElementEndInternal(tag_name);
 				HandleElementEndInternal(tag_name);

+ 4 - 0
Source/Core/Factory.cpp

@@ -280,6 +280,10 @@ void Factory::Initialise()
 	RegisterDataControllerInstancer(&default_instancers.data_controller_event, "event");
 	RegisterDataControllerInstancer(&default_instancers.data_controller_event, "event");
 	RegisterDataControllerInstancer(&default_instancers.data_controller_value, "value");
 	RegisterDataControllerInstancer(&default_instancers.data_controller_value, "value");
 
 
+	// XML nodes that only contain CDATA
+	XMLParser::RegisterPersistentCDATATag("script");
+	XMLParser::RegisterPersistentCDATATag("style");
+
 	// XML node handlers
 	// XML node handlers
 	XMLParser::RegisterNodeHandler("", MakeShared<XMLNodeHandlerDefault>());
 	XMLParser::RegisterNodeHandler("", MakeShared<XMLNodeHandlerDefault>());
 	XMLParser::RegisterNodeHandler("body", MakeShared<XMLNodeHandlerBody>());
 	XMLParser::RegisterNodeHandler("body", MakeShared<XMLNodeHandlerBody>());

+ 30 - 2
Source/Core/XMLParser.cpp

@@ -27,6 +27,7 @@
  */
  */
 
 
 #include "../../Include/RmlUi/Core/XMLParser.h"
 #include "../../Include/RmlUi/Core/XMLParser.h"
+#include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Log.h"
 #include "../../Include/RmlUi/Core/Log.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
@@ -41,6 +42,7 @@ namespace Rml {
 
 
 struct XmlParserData {
 struct XmlParserData {
 	UnorderedMap<String, SharedPtr<XMLNodeHandler>> node_handlers;
 	UnorderedMap<String, SharedPtr<XMLNodeHandler>> node_handlers;
+	UnorderedSet<String> cdata_tags;
 	SharedPtr<XMLNodeHandler> default_node_handler;
 	SharedPtr<XMLNodeHandler> default_node_handler;
 };
 };
 
 
@@ -48,8 +50,8 @@ static ControlledLifetimeResource<XmlParserData> xml_parser_data;
 
 
 XMLParser::XMLParser(Element* root)
 XMLParser::XMLParser(Element* root)
 {
 {
-	RegisterCDATATag("script");
-	RegisterCDATATag("style");
+	for (const String& cdata_tag : xml_parser_data->cdata_tags)
+		RegisterCDATATag(cdata_tag);
 
 
 	for (const String& name : Factory::GetStructuralDataViewAttributeNames())
 	for (const String& name : Factory::GetStructuralDataViewAttributeNames())
 		RegisterInnerXMLAttribute(name);
 		RegisterInnerXMLAttribute(name);
@@ -57,6 +59,19 @@ XMLParser::XMLParser(Element* root)
 	// Add the first frame.
 	// Add the first frame.
 	ParseFrame frame;
 	ParseFrame frame;
 	frame.element = root;
 	frame.element = root;
+	if (root != nullptr)
+	{
+		frame.tag = root->GetTagName();
+		auto itr = xml_parser_data->node_handlers.find(root->GetTagName());
+		if (itr != xml_parser_data->node_handlers.end())
+		{
+			frame.node_handler = itr->second.get();
+		}
+		else
+		{
+			frame.node_handler = xml_parser_data->default_node_handler.get();
+		}
+	}
 	stack.push(frame);
 	stack.push(frame);
 
 
 	active_handler = nullptr;
 	active_handler = nullptr;
@@ -66,6 +81,19 @@ XMLParser::XMLParser(Element* root)
 
 
 XMLParser::~XMLParser() {}
 XMLParser::~XMLParser() {}
 
 
+void XMLParser::RegisterPersistentCDATATag(const String& _tag)
+{
+	if (!xml_parser_data)
+		xml_parser_data.Initialize();
+
+	String tag = StringUtilities::ToLower(_tag);
+
+	if (tag.empty())
+		return;
+
+	xml_parser_data->cdata_tags.insert(tag);
+}
+
 XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr<XMLNodeHandler> handler)
 XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr<XMLNodeHandler> handler)
 {
 {
 	if (!xml_parser_data)
 	if (!xml_parser_data)

+ 2 - 0
Source/SVG/CMakeLists.txt

@@ -8,6 +8,8 @@ target_sources(rmlui_core PRIVATE
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGCache.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGCache.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.cpp"
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.h"
 	"${CMAKE_CURRENT_SOURCE_DIR}/SVGPlugin.h"
+	"${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSVG.cpp"
+	"${CMAKE_CURRENT_SOURCE_DIR}/XMLNodeHandlerSVG.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/SVG/ElementSVG.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/SVG/ElementSVG.h"
 )
 )
 
 

+ 1 - 1
Source/SVG/DecoratorSVG.cpp

@@ -45,7 +45,7 @@ namespace SVG {
 
 
 	DecoratorDataHandle DecoratorSVG::GenerateElementData(Element* element, BoxArea paint_area) const
 	DecoratorDataHandle DecoratorSVG::GenerateElementData(Element* element, BoxArea paint_area) const
 	{
 	{
-		SharedPtr<SVGData> handle = SVGCache::GetHandle(source_path, element, crop_to_content, paint_area);
+		SharedPtr<SVGData> handle = SVGCache::GetHandle(source_path, source_path, SVGCache::File, element, crop_to_content, paint_area);
 		if (!handle)
 		if (!handle)
 			return {};
 			return {};
 
 

+ 58 - 24
Source/SVG/ElementSVG.cpp

@@ -34,8 +34,9 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-ElementSVG::ElementSVG(const String& tag) : Element(tag) {}
+unsigned long ElementSVG::internal_id_counter = 0;
 
 
+ElementSVG::ElementSVG(const String& tag) : Element(tag) {}
 ElementSVG::~ElementSVG()
 ElementSVG::~ElementSVG()
 {
 {
 	handle.reset();
 	handle.reset();
@@ -43,7 +44,7 @@ ElementSVG::~ElementSVG()
 
 
 bool ElementSVG::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio)
 bool ElementSVG::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio)
 {
 {
-	UpdateCachedData();
+	EnsureSourceLoaded();
 
 
 	dimensions = handle ? handle->intrinsic_dimensions : Vector2f(0);
 	dimensions = handle ? handle->intrinsic_dimensions : Vector2f(0);
 
 
@@ -64,14 +65,9 @@ bool ElementSVG::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio)
 	return true;
 	return true;
 }
 }
 
 
-void ElementSVG::EnsureSourceLoaded()
-{
-	UpdateCachedData();
-}
-
 void ElementSVG::OnRender()
 void ElementSVG::OnRender()
 {
 {
-	UpdateCachedData();
+	EnsureSourceLoaded();
 	if (handle)
 	if (handle)
 		handle->geometry.Render(GetAbsoluteOffset(BoxArea::Content), handle->texture);
 		handle->geometry.Render(GetAbsoluteOffset(BoxArea::Content), handle->texture);
 }
 }
@@ -85,13 +81,7 @@ void ElementSVG::OnAttributeChange(const ElementAttributes& changed_attributes)
 {
 {
 	Element::OnAttributeChange(changed_attributes);
 	Element::OnAttributeChange(changed_attributes);
 
 
-	if (changed_attributes.count("src"))
-	{
-		svg_dirty = true;
-		DirtyLayout();
-	}
-
-	if (changed_attributes.count("crop-to-content"))
+	if (changed_attributes.count("src") || changed_attributes.count("crop-to-content"))
 	{
 	{
 		svg_dirty = true;
 		svg_dirty = true;
 		DirtyLayout();
 		DirtyLayout();
@@ -113,23 +103,67 @@ void ElementSVG::OnPropertyChange(const PropertyIdSet& changed_properties)
 	}
 	}
 }
 }
 
 
-void ElementSVG::UpdateCachedData()
+void ElementSVG::GetInnerRML(String& content) const
+{
+	// If the SVG is from a file source don't add anything to the content string.
+	const auto source = GetAttribute<String>("src", "");
+	if (!source.empty())
+		return;
+
+	content += svg_data;
+}
+
+void ElementSVG::SetInnerRML(const String& content)
+{
+	// If the SVG is from a file source don't set the svg xml data on the element.
+	const auto source = GetAttribute<String>("src", "");
+	if (!source.empty())
+		return;
+
+	if (!HasAttribute("rmlui-svgdata-id"))
+		SetAttribute("rmlui-svgdata-id", "svgdata:" + std::to_string(internal_id_counter++));
+
+	svg_data = "" + content;
+	svg_dirty = true;
+	EnsureSourceLoaded();
+}
+
+void ElementSVG::EnsureSourceLoaded()
 {
 {
 	if (!svg_dirty)
 	if (!svg_dirty)
 		return;
 		return;
 
 
 	svg_dirty = false;
 	svg_dirty = false;
 
 
-	const String source = GetAttribute<String>("src", "");
+	const bool crop_to_content = HasAttribute("crop-to-content");
+	const auto source = GetAttribute<String>("src", "");
 	if (source.empty())
 	if (source.empty())
 	{
 	{
-		handle.reset();
-		return;
+		const auto source_id = GetAttribute<String>("rmlui-svgdata-id", "svgdata:undefined");
+		if (handle)
+			handle.reset(); // The old handle won't be re-used so clear it.
+
+		// Build an svg wrapper tag, copying all but src attribute (expected attributes could be width, height, viewBox, etc.)
+		String svg_element_source = "<svg ";
+		ElementAttributes attrs = GetAttributes();
+		for (auto& attr : attrs)
+		{
+			if (attr.first == "src")
+				continue;
+			svg_element_source.append(attr.first);
+			svg_element_source.append("=\"");
+			svg_element_source.append(StringUtilities::EncodeRml(attr.second.Get<String>()));
+			svg_element_source.append("\" ");
+		}
+		svg_element_source.append(">");
+		svg_element_source.append(svg_data);
+		svg_element_source.append("</svg>");
+
+		handle = SVG::SVGCache::GetHandle(source_id, svg_element_source, SVG::SVGCache::Data, this, crop_to_content, BoxArea::Content);
+	}
+	else
+	{
+		handle = SVG::SVGCache::GetHandle(source, source, SVG::SVGCache::File, this, crop_to_content, BoxArea::Content);
 	}
 	}
-
-	const bool crop_to_content = HasAttribute("crop-to-content");
-
-	handle = SVG::SVGCache::GetHandle(source, this, crop_to_content, BoxArea::Content);
 }
 }
-
 } // namespace Rml
 } // namespace Rml

+ 43 - 29
Source/SVG/SVGCache.cpp

@@ -52,14 +52,15 @@
 namespace Rml {
 namespace Rml {
 namespace SVG {
 namespace SVG {
 	struct SVGKey {
 	struct SVGKey {
-		String path;
+		String source_id;
 		Vector2i dimensions;
 		Vector2i dimensions;
 		bool crop_to_content;
 		bool crop_to_content;
 		ColourbPremultiplied colour;
 		ColourbPremultiplied colour;
 
 
 		friend bool operator==(const SVGKey& lhs, const SVGKey& rhs)
 		friend bool operator==(const SVGKey& lhs, const SVGKey& rhs)
 		{
 		{
-			return lhs.path == rhs.path && lhs.dimensions == rhs.dimensions && lhs.crop_to_content == rhs.crop_to_content && lhs.colour == rhs.colour;
+			return lhs.source_id == rhs.source_id && lhs.dimensions == rhs.dimensions && lhs.crop_to_content == rhs.crop_to_content &&
+				lhs.colour == rhs.colour;
 		}
 		}
 	};
 	};
 } // namespace SVG
 } // namespace SVG
@@ -71,7 +72,7 @@ struct hash<::Rml::SVG::SVGKey> {
 	size_t operator()(const ::Rml::SVG::SVGKey& key) const noexcept
 	size_t operator()(const ::Rml::SVG::SVGKey& key) const noexcept
 	{
 	{
 		size_t hash = 0;
 		size_t hash = 0;
-		Rml::Utilities::HashCombine(hash, key.path);
+		Rml::Utilities::HashCombine(hash, key.source_id);
 		Rml::Utilities::HashCombine(hash, key.dimensions.x);
 		Rml::Utilities::HashCombine(hash, key.dimensions.x);
 		Rml::Utilities::HashCombine(hash, key.dimensions.y);
 		Rml::Utilities::HashCombine(hash, key.dimensions.y);
 		Rml::Utilities::HashCombine(hash, key.crop_to_content);
 		Rml::Utilities::HashCombine(hash, key.crop_to_content);
@@ -85,8 +86,8 @@ struct hash<::Rml::SVG::SVGKey> {
 namespace Rml {
 namespace Rml {
 namespace SVG {
 namespace SVG {
 
 
-	static SharedPtr<SVGData> GetHandle(RenderManager& render_manager, String path, Vector2i dimensions, bool crop_to_content,
-		ColourbPremultiplied colour);
+	static SharedPtr<SVGData> GetHandle(RenderManager& render_manager, String source_id, const String& source, SVGCache::SourceType source_type,
+		Vector2i dimensions, bool crop_to_content, ColourbPremultiplied colour);
 	static void ReleaseHandle(SVGData* handle);
 	static void ReleaseHandle(SVGData* handle);
 
 
 	struct SVGGeometry {
 	struct SVGGeometry {
@@ -152,54 +153,62 @@ namespace SVG {
 		return default_value;
 		return default_value;
 	}
 	}
 
 
-	static SharedPtr<SVGData> GetHandle(RenderManager& render_manager, String move_from_path, const Vector2i dimensions, const bool crop_to_content,
-		const ColourbPremultiplied colour)
+	static SharedPtr<SVGData> GetHandle(RenderManager& render_manager, String move_from_id, const String& source, SVGCache::SourceType source_type,
+		const Vector2i dimensions, const bool crop_to_content, const ColourbPremultiplied colour)
 	{
 	{
-		SVGKey key{std::move(move_from_path), dimensions, crop_to_content, colour};
-		const String& path = key.path;
+		SVGKey key{std::move(move_from_id), dimensions, crop_to_content, colour};
+		const String& source_id = key.source_id;
 		auto& documents = svg_cache_data->documents;
 		auto& documents = svg_cache_data->documents;
 		auto& handles = svg_cache_data->handles;
 		auto& handles = svg_cache_data->handles;
 
 
 		const auto it_handle = handles.find(key);
 		const auto it_handle = handles.find(key);
 		if (it_handle != handles.cend())
 		if (it_handle != handles.cend())
 		{
 		{
-			RMLUI_SVG_DEBUG_LOG("Found handle, reusing: %s, (%d, %d), %s, %#x", path.c_str(), dimensions.x, dimensions.y,
+			RMLUI_SVG_DEBUG_LOG("Found handle, reusing: %s, (%d, %d), %s, %#x", source_id.c_str(), dimensions.x, dimensions.y,
 				crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast<const uint32_t*>(&colour[0]));
 				crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast<const uint32_t*>(&colour[0]));
 			SharedPtr<SVGData> result = it_handle->second.lock();
 			SharedPtr<SVGData> result = it_handle->second.lock();
 			RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache");
 			RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache");
 			return result;
 			return result;
 		}
 		}
 
 
-		RMLUI_SVG_DEBUG_LOG("Making new handle: %s, (%d, %d), %s, %#x", path.c_str(), dimensions.x, dimensions.y,
+		RMLUI_SVG_DEBUG_LOG("Making new handle: %s, (%d, %d), %s, %#x", source_id.c_str(), dimensions.x, dimensions.y,
 			crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast<const uint32_t*>(&colour[0]));
 			crop_to_content ? "crop_to_content" : "crop_none", *reinterpret_cast<const uint32_t*>(&colour[0]));
 
 
 		// Find or create a document
 		// Find or create a document
-		auto it_svg_document = documents.find(path);
+		auto it_svg_document = documents.find(source_id);
 		if (it_svg_document == documents.cend())
 		if (it_svg_document == documents.cend())
 		{
 		{
-			RMLUI_SVG_DEBUG_LOG("Loading SVG document from file %s", path.c_str());
 			SVGDocument doc;
 			SVGDocument doc;
+			if (source_type == SVGCache::SourceType::File)
+			{
+				RMLUI_SVG_DEBUG_LOG("Loading SVG document from file %s", source.c_str());
+				String svg_data;
+				if (source.empty() || !GetFileInterface()->LoadFile(source, svg_data))
+				{
+					Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG file %s", source.c_str());
+					return {};
+				}
 
 
-			String svg_data;
-			if (path.empty() || !GetFileInterface()->LoadFile(path, svg_data))
+				// We use a reset-release approach here in case clients use a non-std unique_ptr (lunasvg uses std::unique_ptr)
+				doc.svg_document.reset(lunasvg::Document::loadFromData(svg_data).release());
+			}
+			else
 			{
 			{
-				Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG file %s", path.c_str());
-				return {};
+				RMLUI_SVG_DEBUG_LOG("Loading SVG document from element %s contents", source_id.c_str());
+				// We use a reset-release approach here in case clients use a non-std unique_ptr (lunasvg uses std::unique_ptr)
+				doc.svg_document.reset(lunasvg::Document::loadFromData(source).release());
 			}
 			}
 
 
-			// We use a reset-release approach here in case clients use a non-std unique_ptr (lunasvg uses std::unique_ptr)
-			doc.svg_document.reset(lunasvg::Document::loadFromData(svg_data.data(), svg_data.size()).release());
-
 			if (!doc.svg_document)
 			if (!doc.svg_document)
 			{
 			{
-				Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG data from file %s", path.c_str());
+				Log::Message(Rml::Log::Type::LT_WARNING, "Could not load SVG data for item %s", source_id.c_str());
 				return {};
 				return {};
 			}
 			}
 
 
 			doc.intrinsic_dimensions.x = Math::Max(float(doc.svg_document->width()), 1.0f);
 			doc.intrinsic_dimensions.x = Math::Max(float(doc.svg_document->width()), 1.0f);
 			doc.intrinsic_dimensions.y = Math::Max(float(doc.svg_document->height()), 1.0f);
 			doc.intrinsic_dimensions.y = Math::Max(float(doc.svg_document->height()), 1.0f);
 
 
-			const auto it_inserted = documents.insert_or_assign(path, std::move(doc));
+			const auto it_inserted = documents.insert_or_assign(source_id, std::move(doc));
 			RMLUI_ASSERT(it_inserted.second);
 			RMLUI_ASSERT(it_inserted.second);
 
 
 			it_svg_document = it_inserted.first;
 			it_svg_document = it_inserted.first;
@@ -317,7 +326,7 @@ namespace SVG {
 		auto it_handle = handles.find(key);
 		auto it_handle = handles.find(key);
 		RMLUI_ASSERT(it_handle != handles.cend());
 		RMLUI_ASSERT(it_handle != handles.cend());
 
 
-		const auto it_document = documents.find(key.path);
+		const auto it_document = documents.find(key.source_id);
 		RMLUI_ASSERT(it_document != documents.cend());
 		RMLUI_ASSERT(it_document != documents.cend());
 		SVGDocument& svg_document = it_document->second;
 		SVGDocument& svg_document = it_document->second;
 
 
@@ -376,7 +385,8 @@ namespace SVG {
 		svg_cache_data.Shutdown();
 		svg_cache_data.Shutdown();
 	}
 	}
 
 
-	SharedPtr<SVGData> SVGCache::GetHandle(const String& source, Element* element, const bool crop_to_content, const BoxArea area)
+	SharedPtr<SVGData> SVGCache::GetHandle(const String& source_id, const String& source, SourceType source_type, Element* element,
+		const bool crop_to_content, const BoxArea area)
 	{
 	{
 		RenderManager* render_manager = element->GetRenderManager();
 		RenderManager* render_manager = element->GetRenderManager();
 		if (!render_manager)
 		if (!render_manager)
@@ -388,14 +398,18 @@ namespace SVG {
 		if (dimensions.x == 0 || dimensions.y == 0)
 		if (dimensions.x == 0 || dimensions.y == 0)
 			dimensions = {0, 0};
 			dimensions = {0, 0};
 
 
-		String path;
-		if (ElementDocument* document = element->GetOwnerDocument())
+		if (source_type == File)
 		{
 		{
-			const String document_source_url = StringUtilities::Replace(document->GetSourceURL(), '|', ':');
-			GetSystemInterface()->JoinPath(path, document_source_url, source);
+			String path;
+			if (ElementDocument* document = element->GetOwnerDocument())
+			{
+				const String document_source_url = StringUtilities::Replace(document->GetSourceURL(), '|', ':');
+				GetSystemInterface()->JoinPath(path, document_source_url, source_id);
+			}
+			return Rml::SVG::GetHandle(*render_manager, path, path, source_type, dimensions, crop_to_content, colour);
 		}
 		}
 
 
-		return Rml::SVG::GetHandle(*render_manager, std::move(path), dimensions, crop_to_content, colour);
+		return Rml::SVG::GetHandle(*render_manager, source_id, source, source_type, dimensions, crop_to_content, colour);
 	}
 	}
 
 
 } // namespace SVG
 } // namespace SVG

+ 9 - 2
Source/SVG/SVGCache.h

@@ -53,18 +53,25 @@ namespace SVG {
 
 
 	class SVGCache {
 	class SVGCache {
 	public:
 	public:
+		enum SourceType {
+			File = 1, /// The source is a file path.
+			Data = 2  /// The source is raw SVG data.
+		};
 		static void Initialize();
 		static void Initialize();
 		static void Shutdown();
 		static void Shutdown();
 
 
 		/// Returns a handle to SVG data matching the parameters - creates new data if none is found.
 		/// Returns a handle to SVG data matching the parameters - creates new data if none is found.
-		/// @param[in] source Path to a file containing the SVG source data.
+		/// @param[in] source_id Key to be used for caching the SVG data.
+		/// @param[in] source SVG source data or Path to a file containing the SVG source data (type specified by source_type).
+		/// @param[in] source_type If source refers to a file or is the SVG source data itself.
 		/// @param[in] element Element for which to calculate the dimensions and color.
 		/// @param[in] element Element for which to calculate the dimensions and color.
 		/// @param[in] crop_to_content Crop the rendered SVG to its contents.
 		/// @param[in] crop_to_content Crop the rendered SVG to its contents.
 		/// @param[in] area The area of the element used to determine the SVG dimensions.
 		/// @param[in] area The area of the element used to determine the SVG dimensions.
 		/// @return A handle to the SVG data, with automatic reference counting.
 		/// @return A handle to the SVG data, with automatic reference counting.
 		///	@note When changing color or dimensions of an SVG without changing the source file, it's best to get a
 		///	@note When changing color or dimensions of an SVG without changing the source file, it's best to get a
 		/// new handle before releasing the old one, to avoid unnecessarily reloading data.
 		/// new handle before releasing the old one, to avoid unnecessarily reloading data.
-		static SharedPtr<SVGData> GetHandle(const String& source, Element* element, bool crop_to_content, BoxArea area);
+		static SharedPtr<SVGData> GetHandle(const String& source_id, const String& source, SourceType source_type, Element* element,
+			const bool crop_to_content, const BoxArea area);
 	};
 	};
 
 
 } // namespace SVG
 } // namespace SVG

+ 4 - 0
Source/SVG/SVGPlugin.cpp

@@ -34,6 +34,7 @@
 #include "../../Include/RmlUi/SVG/ElementSVG.h"
 #include "../../Include/RmlUi/SVG/ElementSVG.h"
 #include "DecoratorSVG.h"
 #include "DecoratorSVG.h"
 #include "SVGCache.h"
 #include "SVGCache.h"
+#include "XMLNodeHandlerSVG.h"
 
 
 namespace Rml {
 namespace Rml {
 namespace SVG {
 namespace SVG {
@@ -50,6 +51,9 @@ namespace SVG {
 			decorator_instancer = MakeUnique<DecoratorSVGInstancer>();
 			decorator_instancer = MakeUnique<DecoratorSVGInstancer>();
 			Factory::RegisterDecoratorInstancer("svg", decorator_instancer.get());
 			Factory::RegisterDecoratorInstancer("svg", decorator_instancer.get());
 
 
+			XMLParser::RegisterNodeHandler("svg", MakeShared<XMLNodeHandlerSVG>());
+			XMLParser::RegisterPersistentCDATATag("svg");
+
 			Log::Message(Log::LT_INFO, "SVG plugin initialised.");
 			Log::Message(Log::LT_INFO, "SVG plugin initialised.");
 		}
 		}
 
 

+ 42 - 0
Source/SVG/XMLNodeHandlerSVG.cpp

@@ -0,0 +1,42 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019- The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "XMLNodeHandlerSVG.h"
+
+namespace Rml {
+namespace SVG {
+	bool XMLNodeHandlerSVG::ElementData(XMLParser* parser, const String& data, XMLDataType /*type*/)
+	{
+		auto* element = rmlui_dynamic_cast<ElementSVG*>(parser->GetParseFrame()->element);
+		RMLUI_ASSERT(element);
+		if (element)
+			element->SetInnerRML(data);
+		return true;
+	}
+} // namespace SVG
+} // namespace Rml

+ 49 - 0
Source/SVG/XMLNodeHandlerSVG.h

@@ -0,0 +1,49 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019- The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_XMLNODEHANDLERSVG_H
+#define RMLUI_CORE_XMLNODEHANDLERSVG_H
+
+#include "../../Include/RmlUi/Core/XMLParser.h"
+#include "../../Include/RmlUi/SVG/ElementSVG.h"
+#include "../Core/XMLNodeHandlerDefault.h"
+
+namespace Rml {
+namespace SVG {
+	/**
+	    Element Node handler that processes the SVG tag
+	 */
+	class XMLNodeHandlerSVG : public XMLNodeHandlerDefault {
+	public:
+		/// Called for element data
+		bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override;
+	};
+
+} // namespace SVG
+} // namespace Rml
+#endif