From 208cee1f6967db42329a3e665401ef51b3f6d9ff Mon Sep 17 00:00:00 2001
From: Sam Moore <matches@ucc.asn.au>
Date: Tue, 12 Aug 2014 14:08:00 +0800
Subject: [PATCH] Deal with groups in SVG parsing

ParseSVGNode will recursively parse <svg>, <g> and <group> nodes.

Basically just going to try and implement a bunch more of SVG now.

The "transform" attribute would be a useful one to deal with because svg editors (at least, inkscape)
seem to be rather liberal in using it.

I hope the timestamp for this is correct, I ran git commit --amend but
I don't know if that will fix it.
---
 src/document.cpp | 113 +++++++++++++++++++++++++----------------------
 src/document.h   |  18 +++++++-
 2 files changed, 75 insertions(+), 56 deletions(-)

diff --git a/src/document.cpp b/src/document.cpp
index a6c6fad..8f35644 100644
--- a/src/document.cpp
+++ b/src/document.cpp
@@ -3,6 +3,8 @@
 #include <cstdio>
 #include <fstream>
 
+#include "../contrib/pugixml-1.4/src/pugixml.cpp"
+
 #include "stb_truetype.h"
 
 using namespace IPDF;
@@ -260,53 +262,50 @@ bool Document::operator==(const Document & equ) const
 }
 
 
-#include "../contrib/pugixml-1.4/src/pugixml.hpp"
-#include "../contrib/pugixml-1.4/src/pugixml.cpp"
 
-/**
- * Load an SVG into a rectangle
- */
-void Document::LoadSVG(const string & filename, const Rect & bounds)
-{
-	using namespace pugi;
-	
-	xml_document doc_xml;
-	ifstream input(filename.c_str(), ios_base::in);
-	xml_parse_result result = doc_xml.load(input);
-	
-	if (!result)
-		Fatal("Couldn't load \"%s\" - %s", filename.c_str(), result.description());
-		
-	Debug("Loaded XML - %s", result.description());
-	
-	input.close();
 
-	// Combine all SVG tags into one thing because lazy
-	for (xml_node svg = doc_xml.child("svg"); svg; svg = svg.next_sibling("svg"))
+void Document::ParseSVGNode(pugi::xml_node & root, const Rect & bounds, Real & width, Real & height)
+{
+	Debug("Parse node <%s>", root.name());
+	pugi::xml_attribute attrib_w = root.attribute("width");
+	pugi::xml_attribute attrib_h = root.attribute("height");
+	if (!attrib_w.empty())
+		width = attrib_w.as_float() * bounds.w;
+	if (!attrib_h.empty())
+		height = attrib_h.as_float() * bounds.h;
+			
+	for (pugi::xml_node child = root.first_child(); child; child = child.next_sibling())
 	{
-		Real width = Real(svg.attribute("width").as_float()) * bounds.w;
-		Real height = Real(svg.attribute("width").as_float()) * bounds.h;
-		
+
 		
-		// Rectangles
-		Real coords[4];
-		const char * attrib_names[] = {"x", "y", "width", "height"};
-		for (pugi::xml_node rect = svg.child("rect"); rect; rect = rect.next_sibling("rect"))
+		if (strcmp(child.name(), "svg") == 0 || strcmp(child.name(),"g") == 0
+			|| strcmp(child.name(), "group") == 0)
+		{
+			//TODO: Handle translates etc here
+			ParseSVGNode(child, bounds, width, height);
+			continue;
+		}
+		else if (strcmp(child.name(), "path") == 0)
+		{
+			string d = child.attribute("d").as_string();
+			Debug("Path data attribute is \"%s\"", d.c_str());
+			ParseSVGPathData(d, Rect(bounds.x,bounds.y,width,height));
+		}
+		else if (strcmp(child.name(), "rect") == 0)
 		{
+			Real coords[4];
+			const char * attrib_names[] = {"x", "y", "width", "height"};
 			for (size_t i = 0; i < 4; ++i)
-				coords[i] = rect.attribute(attrib_names[i]).as_float();
+				coords[i] = child.attribute(attrib_names[i]).as_float();
 			
-			bool outline = !(rect.attribute("fill"));
+			bool outline = !(child.attribute("fill"));
 			Add(outline?RECT_OUTLINE:RECT_FILLED, Rect(coords[0]/width + bounds.x, coords[1]/height + bounds.y, coords[2]/width, coords[3]/height),0);
-			Debug("Added rectangle");
-		}		
-		
-		// Circles
-		for (pugi::xml_node circle = svg.child("circle"); circle; circle = circle.next_sibling("circle"))
+		}
+		else if (strcmp(child.name(), "circle") == 0)
 		{
-			Real cx = circle.attribute("cx").as_float();
-			Real cy = circle.attribute("cy").as_float();
-			Real r = circle.attribute("r").as_float();
+			Real cx = child.attribute("cx").as_float();
+			Real cy = child.attribute("cy").as_float();
+			Real r = child.attribute("r").as_float();
 			
 			Real x = (cx - r)/width + bounds.x; 
 			Real y = (cy - r)/height + bounds.y; 
@@ -315,25 +314,31 @@ void Document::LoadSVG(const string & filename, const Rect & bounds)
 			
 			Rect rect(x,y,w,h);
 			Add(CIRCLE_FILLED, rect,0);
-			Debug("Added Circle %s", rect.Str().c_str());
-
-		}		
-		
-		// paths
-		for (pugi::xml_node path = svg.child("path"); path; path = path.next_sibling("path"))
-		{
-			
-			string d = path.attribute("d").as_string();
-			Debug("Path data attribute is \"%s\"", d.c_str());
-			AddPathFromString(d, Rect(bounds.x,bounds.y,width,height));
-			
+			Debug("Added Circle %s", rect.Str().c_str());			
 		}
 	}
+}
+
+/**
+ * Load an SVG into a rectangle
+ */
+void Document::LoadSVG(const string & filename, const Rect & bounds)
+{
+	using namespace pugi;
 	
-	//Fatal("Done");
+	xml_document doc_xml;
+	ifstream input(filename.c_str(), ios_base::in);
+	xml_parse_result result = doc_xml.load(input);
 	
+	if (!result)
+		Fatal("Couldn't load \"%s\" - %s", filename.c_str(), result.description());
+		
+	Debug("Loaded XML - %s", result.description());
 	
-
+	input.close();
+	Real width(1);
+	Real height(1);
+	ParseSVGNode(doc_xml, bounds,width,height);
 }
 
 // Behold my amazing tokenizing abilities
@@ -357,14 +362,14 @@ static string & GetToken(const string & d, string & token, unsigned & i)
 		}
 		token += d[i++];
 	}
-	Debug("Got token \"%s\"", token.c_str());
+	//Debug("Got token \"%s\"", token.c_str());
 	return token;
 }
 
 
 // Fear the wrath of the tokenizing svg data
 // Seriously this isn't really very DOM-like at all is it?
-void Document::AddPathFromString(const string & d, const Rect & bounds)
+void Document::ParseSVGPathData(const string & d, const Rect & bounds)
 {
 	Real x[4] = {0,0,0,0};
 	Real y[4] = {0,0,0,0};
diff --git a/src/document.h b/src/document.h
index 62f624c..8e63f5b 100644
--- a/src/document.h
+++ b/src/document.h
@@ -4,6 +4,9 @@
 #include "ipdf.h"
 #include "quadtree.h"
 
+#include "../contrib/pugixml-1.4/src/pugixml.hpp"
+
+
 typedef struct stbtt_fontinfo stbtt_fontinfo;
 
 namespace IPDF
@@ -14,7 +17,7 @@ namespace IPDF
 			Document(const std::string & filename = "") : m_objects(), m_count(0) {Load(filename);}
 			virtual ~Document() {}
 			
-			void LoadSVG(const std::string & filename, const Rect & bounds = Rect(0,0,1,1));
+			
 
 			void Load(const std::string & filename = "");
 			void Save(const std::string & filename);
@@ -29,7 +32,18 @@ namespace IPDF
 			void Add(ObjectType type, const Rect & bounds, unsigned data_index = 0);
 			unsigned AddBezierData(const Bezier & bezier);
 			
-			void AddPathFromString(const std::string & d, const Rect & bounds);
+			
+			
+			
+			/** SVG Related functions **/
+			
+			/** Load an SVG text file and add to the document **/
+			void LoadSVG(const std::string & filename, const Rect & bounds = Rect(0,0,1,1));
+			
+			/** Parse an SVG node or SVG-group node, adding children to the document **/
+			void ParseSVGNode(pugi::xml_node & root, const Rect & bounds, Real & width, Real & height);
+			/** Parse an SVG path with string **/
+			void ParseSVGPathData(const std::string & d, const Rect & bounds);
 
 			void AddFontGlyphAtPoint(stbtt_fontinfo *font, int character, Real scale, Real x, Real y);
 
-- 
2.20.1