]> gitweb.michael.orlitzky.com - libsvgtiny-pixbuf.git/blobdiff - io-svg.c
COPYING: add one to state the "or later" bit
[libsvgtiny-pixbuf.git] / io-svg.c
index 341d9e5939ed74f0fe9bb32803513bb7a2420941..8ef9ff90e3c1bfb73dcbd7e3bb919475d9495f4c 100644 (file)
--- a/io-svg.c
+++ b/io-svg.c
@@ -1,38 +1,54 @@
-#include <stdio.h> /* fprintf, printf */
-#include <string.h> /* memcpy */
+#include <string.h> /* memcpy, memset */
 
 #include <cairo.h>
-#include <gdk/gdk.h>
 #include <gdk-pixbuf/gdk-pixbuf.h> /* includes glib.h */
+#include <glib/gprintf.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
 #include <svgtiny.h>
 
-/*
- * The width and height of the viewport that we'll render the SVG
+/* This "header" includes extra code that we've copy/pasted from GDK */
+#include "gdk_pixbuf_get_from_surface.h"
+
+/* Declare the two functions that we export. Their names aren't
+ * mentioned in gdk-pixbuf-io.h, but they are required by the
+ * implementation, as you can confirm from the API docs or in
+ * gdk-pixbuf-io.c. The G_MODULE_EXPORT macro is defined in
+ * glib's gmodule.h */
+G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module);
+G_MODULE_EXPORT void fill_info(GdkPixbufFormat *info);
+
+
+/* The width and height of the viewport that we'll render the SVG
  * into. The final "picture" may not actually be this size; based on
  * the height, width, viewBox, and preserveAspectRatio attributes in
  * the SVG itself, libsvgtiny may scale, stretch, offset, etc. the
- * paths to make them fit nicely into the viewport.
- */
+ * paths to make them fit nicely into the viewport. */
 #define VIEWPORT_WIDTH 512
 #define VIEWPORT_HEIGHT 512
 
-#define SVG_BUFFER_INCREMENT (size_t)4194304
-
 /* Convenient typedefs for libsvgtiny */
 typedef struct svgtiny_diagram diagram_t;
 typedef struct svgtiny_shape shape_t;
 
 
+/* Our main data structure. One of these is created when we start
+ * loading an SVG, and it persists while the SVG is being read. It
+ * contains a few boilerplate members, and the svg_data/svg_data_size
+ * fields that we use to keep track of the buffer where the SVG file
+ * lives. */
 typedef struct {
   GdkPixbufModuleUpdatedFunc  updated_func;
   GdkPixbufModulePreparedFunc prepared_func;
-  GdkPixbufModuleSizeFunc     size_func;
   gpointer                    user_data;
 
-  /* The "file" */
-  char*                       svg_data;
-  size_t                      svg_data_size;
-  size_t                      svg_data_max;
+  /* The SVG "file" that we're building in memory. */
+  gchar*                      svg_data;
+
+  /* How far into svg_data are we? This should always point to the
+     next empty byte. If (for example) svg_data_size is 2, then
+     svg_data[0] and svg_data[1] are used, but svg_data[2] is free. */
+  size_t svg_data_size;
 
 } SvgTinyContext;
 
@@ -40,6 +56,9 @@ typedef struct {
 /**
  * @brief Render an svgtiny path using cairo.
  *
+ * This was lovingly borrowed from @c examples/svgtiny_display_x11.c
+ * in libsvgtiny itself, and modified to use a transparent background.
+ *
  * @param cr
  *   A pointer to a valid cairo context.
  *
@@ -55,8 +74,8 @@ static void render_path(cairo_t* cr, shape_t* path) {
     switch ((int) path->path[j]) {
     case svgtiny_PATH_MOVE:
       cairo_move_to(cr,
-                   path->path[j + 1],
-                   path->path[j + 2]);
+                    path->path[j + 1],
+                    path->path[j + 2]);
       j += 3;
       break;
     case svgtiny_PATH_CLOSE:
@@ -112,28 +131,47 @@ static void render_path(cairo_t* cr, shape_t* path) {
  *   returned; if not, @c NULL is returned. You are expected to @c
  *   svgtiny_free the result if it is valid.
  */
-static diagram_t* svgtiny_diagram_from_buffer(char* buffer,
-                                             size_t bytecount,
-                                             int width,
-                                             int height,
-                                             GError** error) {
+static diagram_t* svgtiny_diagram_from_buffer(const gchar* buffer,
+                                              gsize bytecount,
+                                              guint width,
+                                              guint height,
+                                              GError** error) {
   diagram_t* diagram;
   svgtiny_code code;
 
   diagram = svgtiny_create();
   if (!diagram) {
     g_set_error_literal(error,
-                        GDK_PIXBUF_ERROR,
-                        GDK_PIXBUF_ERROR_FAILED,
-                        "svgtiny_create() failed");
+                        G_FILE_ERROR,
+                        G_FILE_ERROR_NOMEM,
+                        "out of memory in svgtiny_create()");
     return NULL;
   }
 
-  code = svgtiny_parse(diagram, buffer, bytecount, "", width, height);
-  free(buffer);
+  g_assert((int)width >= 0);
+  g_assert((int)height >= 0);
 
-  if (code != svgtiny_OK) {
-    switch (code) {
+  /* There's a thread-safety issue in libwapcaplet that can cause
+   * svgtiny_parse() to crash if you load lots of SVGs at once:
+   *
+   *   https://bugs.netsurf-browser.org/mantis/view.php?id=2857
+   *
+   * Putting a lock around svgtiny_parse() is a pretty simple solution
+   * and looks like it does the trick.
+  */
+  static GMutex mutex;
+  g_mutex_lock(&mutex);
+  code = svgtiny_parse(diagram,
+                       buffer,
+                       bytecount, "",
+                       (int)width,
+                       (int)height);
+  g_mutex_unlock (&mutex);
+
+  switch(code) {
+    case svgtiny_OK:
+      /* The one success case. */
+      return diagram;
     case svgtiny_OUT_OF_MEMORY:
       g_set_error_literal(error,
                           G_FILE_ERROR,
@@ -144,27 +182,26 @@ static diagram_t* svgtiny_diagram_from_buffer(char* buffer,
       g_set_error_literal(error,
                           GDK_PIXBUF_ERROR,
                           GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                          "libdom error in svgtiny_parse()");
+                          "invalid XML DOM in svgtiny_parse()");
       break;
     case svgtiny_NOT_SVG:
       g_set_error_literal(error,
                           GDK_PIXBUF_ERROR,
                           GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                          "encountered svgtiny_NOT_SVG in svgtiny_parse()");
+                          "missing <svg> element in svgtiny_parse()");
       break;
     case svgtiny_SVG_ERROR:
       g_set_error(error,
                   GDK_PIXBUF_ERROR,
                   GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                  "SVG error in svgtiny_parse() on line %i: %s",
+                  "SVG format error in svgtiny_parse() on line %i: %s",
                   diagram->error_line,
                   diagram->error_message);
       break;
-    }
-    return NULL;
   }
 
-  return diagram;
+  /* All other cases above are failure */
+  return NULL;
 }
 
 /**
@@ -177,7 +214,7 @@ static diagram_t* svgtiny_diagram_from_buffer(char* buffer,
  *   is returned; if not, @c NULL is returned. You are expected to @c
  *   cairo_destroy the result if it is valid.
  */
-static cairo_t* cairo_context_from_diagram(diagram_t* diagram) {
+static cairo_t* cairo_context_from_diagram(const diagram_t* diagram) {
   cairo_t* cr;
   cairo_surface_t* surface;
   cairo_status_t crs;
@@ -189,9 +226,9 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) {
 
   crs = cairo_surface_status(surface);
   if (crs != CAIRO_STATUS_SUCCESS) {
-    fprintf(stderr,
-            "cairo_image_surface_create failed: %s\n",
-            cairo_status_to_string(crs));
+    g_fprintf(stderr,
+              "cairo_image_surface_create failed: %s\n",
+              cairo_status_to_string(crs));
     cairo_surface_destroy(surface);
     return NULL;
   }
@@ -204,9 +241,9 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) {
   cairo_surface_destroy(surface);
 
   if (crs != CAIRO_STATUS_SUCCESS) {
-    fprintf(stderr,
-            "cairo_create failed: %s\n",
-            cairo_status_to_string(crs));
+    g_fprintf(stderr,
+              "cairo_create failed: %s\n",
+              cairo_status_to_string(crs));
     cairo_destroy(cr);
     return NULL;
   }
@@ -246,9 +283,9 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) {
   /* Check the status again just for good measure? */
   crs = cairo_status(cr);
   if (crs != CAIRO_STATUS_SUCCESS) {
-    fprintf(stderr,
-           "cairo error: %s\n",
-           cairo_status_to_string(crs));
+    g_fprintf(stderr,
+              "cairo error: %s\n",
+              cairo_status_to_string(crs));
     cairo_destroy(cr);
     return NULL;
   }
@@ -271,19 +308,19 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) {
  * @return If successful, a valid pointer to a @c GdkPixbuf is
  *   returned; if not, @c NULL is returned and @c error is populated.
  */
-static GdkPixbuf* gdk_pixbuf_from_svg_buffer(char* buffer,
-                                            size_t bytecount,
-                                            GError** error) {
+static GdkPixbuf* gdk_pixbuf_from_svg_buffer(const gchar* buffer,
+                                             gsize bytecount,
+                                             GError** error) {
   diagram_t* diagram;
   cairo_t* cr = 0;
   GdkPixbuf* pb;
   GError* sub_error = NULL;
 
   diagram = svgtiny_diagram_from_buffer(buffer,
-                                       bytecount,
-                                       VIEWPORT_WIDTH,
-                                       VIEWPORT_HEIGHT,
-                                       &sub_error);
+                                        bytecount,
+                                        VIEWPORT_WIDTH,
+                                        VIEWPORT_HEIGHT,
+                                        &sub_error);
   if (!diagram) {
     g_propagate_error(error, sub_error);
     return NULL;
@@ -293,100 +330,103 @@ static GdkPixbuf* gdk_pixbuf_from_svg_buffer(char* buffer,
   if (!cr) {
     svgtiny_free(diagram);
     g_set_error_literal(error,
-                       GDK_PIXBUF_ERROR,
-                       GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                       "could not create Cairo surface from SVG diagram");
+                        GDK_PIXBUF_ERROR,
+                        GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                        "could not create Cairo surface from SVG diagram");
     return NULL;
   }
 
 
-  /* We're using the viewport width and height and not the diagram
-   * width/height for the image. The diagram can be of a different
-   * size and aspect ratio than the viewport, and our main use case is
-   * for icons that are generally square and reasonably sized. If the
-   * diagram is "small," then we want to scale it up until it fits
-   * nicely in the viewport before rendering it. That's as opposed to
-   * rendering the image small, and letting GDK scale it up. Of course
-   * this reasoning makes the assumption that the viewport is usually
-   * larger than the diagram.
-  */
+  /* I've gone back and forth on this about five times: we use the
+   * diagram width and height, and not the viewport width and height.
+   * This can ultimately render an image that's larger than the
+   * viewport size, but I think GDK will resize the final pixbuf
+   * anyway. More importantly, rendering small icons at a larger
+   * (viewport) size seems to make the whole thing go ape-shit.
+   * So for now I'm back in the diagram camp.
+   */
   pb = gdk_pixbuf_get_from_surface(cairo_get_target(cr),
-                                  0,
-                                  0,
-                                  VIEWPORT_WIDTH,
-                                  VIEWPORT_HEIGHT);
+                                   0,
+                                   0,
+                                   diagram->width,
+                                   diagram->height);
 
 
   if (!pb) {
     g_set_error_literal(error,
-                       GDK_PIXBUF_ERROR,
-                       GDK_PIXBUF_ERROR_FAILED,
-                       "failed to obtain a GdkPixbuf from Cairo surface");
+                        GDK_PIXBUF_ERROR,
+                        GDK_PIXBUF_ERROR_FAILED,
+                        "failed to obtain a GdkPixbuf from Cairo surface");
   }
 
   return pb;
 }
 
 
+/**
+ * @brief Our implementation of GdkPixbufModuleBeginLoadFunc, as
+ *   defined in gdk-pixbuf-io.h.
+ */
 static gpointer gdk_pixbuf_begin_load(GdkPixbufModuleSizeFunc size_func,
-                                     GdkPixbufModulePreparedFunc prep_func,
-                                     GdkPixbufModuleUpdatedFunc updated_func,
-                                     gpointer user_data,
-                                     GError **error) {
+                                      GdkPixbufModulePreparedFunc prep_func,
+                                      GdkPixbufModuleUpdatedFunc updated_func,
+                                      gpointer user_data,
+                                      GError **error) {
 
   SvgTinyContext* context = g_new(SvgTinyContext, 1);
 
-  context->size_func      = size_func;
   context->prepared_func  = prep_func;
   context->updated_func   = updated_func;
   context->user_data      = user_data;
 
-  /* YOLO, no error checking */
-  context->svg_data       = g_malloc(SVG_BUFFER_INCREMENT);
+  context->svg_data       = NULL;
   context->svg_data_size  = 0;
 
   return context;
 }
 
+
+/**
+ * @brief Our implementation of GdkPixbufModuleIncrementLoadFunc, as
+ *   defined in gdk-pixbuf-io.h.
+ */
 static gboolean gdk_pixbuf_load_increment(gpointer data,
-                                         const guchar* buf,
-                                         guint size,
-                                         GError** error) {
-  size_t increment = 0;
+                                          const guchar* buf,
+                                          guint buf_size,
+                                          GError** error) {
   SvgTinyContext* context = (SvgTinyContext*)data;
 
-  if (context->svg_data_size + size > context->svg_data_max) {
-    if (size > SVG_BUFFER_INCREMENT) {
-      increment = size;
-    }
-    else {
-      increment = SVG_BUFFER_INCREMENT;
-    }
-
-    /* YOLO, no error checking */
-    context->svg_data = g_realloc(context->svg_data,
-                                 context->svg_data_max + increment);
-
-    context->svg_data_max += increment;
+  if (buf_size == 0) {
+    return TRUE;
   }
 
-  memcpy(context->svg_data + context->svg_data_size, buf, size);
-  context->svg_data_size += size;
+  context->svg_data = g_realloc(context->svg_data,
+                                context->svg_data_size + buf_size);
+  memcpy(context->svg_data + context->svg_data_size, buf, buf_size);
+  context->svg_data_size += buf_size;
 
   return TRUE;
 }
 
+/**
+ * @brief Convenience function to execute the "updated" callback
+ *   stored in our @c context.
+ */
 static void emit_updated(SvgTinyContext* context, GdkPixbuf* pixbuf) {
   if (context->updated_func != NULL) {
     (*context->updated_func)(pixbuf,
-                            0,
-                            0,
-                            gdk_pixbuf_get_width(pixbuf),
-                            gdk_pixbuf_get_height(pixbuf),
-                            context->user_data);
+                             0,
+                             0,
+                             gdk_pixbuf_get_width(pixbuf),
+                             gdk_pixbuf_get_height(pixbuf),
+                             context->user_data);
   }
 }
 
+/**
+ * @brief Convenience function to execute the "prepared" callback
+ *   stored in our @c context.
+ */
 static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
   if (context->prepared_func != NULL) {
     (*context->prepared_func)(pixbuf, NULL, context->user_data);
@@ -394,51 +434,282 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
 }
 
 
-/*
-static void emit_size(SvgTinyContext* context, GdkPixbuf* pixbuf) {
-  int w = gdk_pixbuf_get_width(pixbuf);
-  int h = gdk_pixbuf_get_height(pixbuf);
-  if (context->size_func != NULL) {
-    (*context->size_func)(&w, &h, context->user_data);
+
+/**
+ * @brief Replace one GTK <xi:include> element by its data.
+ *
+ * @param node
+ *   A pointer to an <xi:include> element node.
+ *
+ * @return TRUE if we replaced the node, and FALSE otherwise.
+ *
+ */
+static gboolean process_one_xinclude(xmlNode* node) {
+  xmlChar* href;
+
+  href = xmlGetProp(node, BAD_CAST "href");
+  if (href == NULL) {
+    /* We only process XIncludes with base64 data hrefs */
+    return FALSE;
+  }
+
+  if (xmlStrncmp(href, BAD_CAST "data:text/xml;base64,", 21)) {
+    /* We only process XIncludes with base64 data hrefs */
+    return FALSE;
   }
+
+  xmlChar* hrefdata = href+21;
+
+  /* Verify that hrefdata is base64-encoded (and that it's safe to
+     cast to a signed gchar pointer). I'm assuming that everyone is
+     using the RFC 4648 encoding? */
+  for (unsigned int i=0; i < xmlStrlen(hrefdata); i++) {
+    if (hrefdata[i] > 'z') {
+      return FALSE;
+    }
+    if (hrefdata[i] < '0' && hrefdata[i] != '+' && hrefdata[i] != '/') {
+      return FALSE;
+    }
+  }
+
+  /* WARNING: the xmlChar and guchar types here are compatible, but
+     the decoded data is not necessarily NULL-terminated, while all of
+     the libxml2 functions that operate on a xmlChar pointer assume
+     that they are. */
+  gsize decoded_size;
+  xmlChar* decoded = g_base64_decode((const gchar*)hrefdata, &decoded_size);
+
+  /* This cast is safe because signed and unsigned chars are the same size,
+     and xmlReadMemory is going to treat the data as binary anyway. */
+  xmlDoc* xinc_doc = xmlReadMemory((const char*)decoded,
+                                   decoded_size,
+                                   "xinclude.xml",
+                                   NULL,
+                                   0);
+  g_free(decoded);
+
+  if (xinc_doc == NULL) {
+    return FALSE;
+  }
+
+  xmlNode* xinc_root = xmlDocGetRootElement(xinc_doc);
+  if (xinc_root == NULL || xmlStrcmp(xinc_root->name, BAD_CAST "svg")) {
+    return FALSE;
+  }
+
+  /* Replace the original xinclude "node" with the children of this
+     "svg" node. Do the order of the nodes in an SVG matter? I don't
+     know, but we go to a little bit of extra trouble here to ensure
+     that we put the replacement in the right place, i.e. after its
+     previous sibling (if there is one). */
+
+  xmlNode* p = xmlPreviousElementSibling(node);
+  xmlNode* cur_node;
+
+  /* If there is no previous sibling element, do one AddChild()
+     first. Then we're back to the case of a previous sibling. */
+  if (p) {
+    cur_node = xmlFirstElementChild(xinc_root);
+  }
+  else {
+    p = node->parent;
+    cur_node = xmlFirstElementChild(xinc_root);
+    if (cur_node) {
+      /* Without the xmlCopyNode, I get segfaults, and I don't care to
+        investigate why. */
+      p = xmlAddChild(p, xmlCopyNode(cur_node,1));
+      xmlReconciliateNs(p->doc, p);
+
+      cur_node = xmlNextElementSibling(cur_node);
+    }
+  }
+
+  g_assert(p != NULL); /* xmlAddChild didn't fail */
+
+  xmlUnlinkNode(node);
+  xmlFreeNode(node);
+
+  while (cur_node) {
+    p = xmlAddNextSibling(p, xmlCopyNode(cur_node,1));
+    xmlReconciliateNs(p->doc, p);
+    cur_node = xmlNextElementSibling(cur_node);
+  }
+
+  xmlFreeDoc(xinc_doc);
+
+  return TRUE;
+}
+
+/**
+ * @brief Replace all GTK <xi:include> elements in a tree by their data.
+ *
+ * @param node
+ *   A node pointer, to the root of the tree.
+ *
+ * @return TRUE if we replaced any <xi:include> element nodes, and
+ *   FALSE otherwise.
+ *
+ */
+static gboolean process_child_xincludes(xmlNode* a_node) {
+  gboolean result = FALSE;
+  xmlNode* cur_node = a_node;
+  xmlNode* next_node;
+
+  g_assert(cur_node == NULL || cur_node->type == XML_ELEMENT_NODE);
+
+  while (cur_node) {
+    if (!xmlStrcmp(cur_node->name, BAD_CAST "include")) {
+      /* process_one_xinclude() clobbers this node, so we need
+         to get its successor before calling that function. */
+      next_node = xmlNextElementSibling(cur_node);
+      if (process_one_xinclude(cur_node)) {
+        result = TRUE;
+      }
+      cur_node = next_node;
+      continue;
+    }
+
+    if (process_child_xincludes(xmlFirstElementChild(cur_node))) {
+      result = TRUE;
+    }
+    cur_node = xmlNextElementSibling(cur_node);
+  }
+
+  return result;
+}
+
+
+/**
+ * @brief Process GTK <xi:include> elements in an SVG buffer.
+ *
+ * GTK is very cute. Its gtk-encode-symbolic-svg tool wraps your SVG
+ * in its own boilerplate, but then rather than including your SVG
+ * data verbatim, it includes it via a sketchy XInclude that looks
+ * like,
+ *
+ *   <xi:include href="data:text/xml;base64,PD94bWwgd..."/>
+ *
+ * Librsvg knows how to parse that, but libxml2 doesn't (the latter
+ * can handle an XInclude, just not a base64-encoded data reference).
+ * Fortunately, you can read the source to gtk-encode-symbolic-svg,
+ * and just see what the format of that line will be. Here we're going
+ * to parse out the base64 data, decode it, strip out its opening
+ * <xml> and <svg> tags, and then replace the original <xi:include>
+ * element with the result.
+ *
+ * @param buffer
+ *   A buffer containing SVG file data.
+ *
+ * @param buf_size
+ *   The size of @c buffer (which may not be NULL-terminated).
+ *
+ * @param new_size
+ *   A pointer to the size of the new buffer, valid only if the
+ *   return value is non-NULL.
+ *
+ * @return A pointer to a buffer where the <xi:include> has been
+ * processed. If no replacements were made, the result will be @c
+ * NULL; otherwise, you are expected to @c free it when you are done.
+ */
+static gchar* process_gtk_symbolic_svg_xinclude(const gchar* buffer,
+                                                gsize buf_size,
+                                                gsize* new_size) {
+
+  xmlDoc* doc = xmlReadMemory(buffer,buf_size,"symbolic.xml",NULL,0);
+  if (doc == NULL) {
+    return NULL;
+  }
+
+  xmlNode* root_element = xmlDocGetRootElement(doc);
+  if (root_element == NULL) {
+    return NULL;
+  }
+
+  gchar* result = NULL;
+  if (process_child_xincludes(root_element)) {
+    /* If we actually replaced something, we need to return the new
+       document in a buffer. */
+    xmlChar *xmlbuf;
+    int xmlbuf_size;
+    xmlDocDumpFormatMemory(doc, &xmlbuf, &xmlbuf_size, 1);
+    /* We're going to free() this later on with g_free() instead of
+       xmlFree(), so the two "byte" types had better be the same
+       size. */
+    g_assert(sizeof(xmlChar) == sizeof(gchar));
+    *new_size = (gsize)xmlbuf_size;
+    result = (gchar*)xmlbuf;
+  }
+
+  xmlFreeDoc(doc);
+  xmlCleanupParser();
+  return result;
 }
-*/
 
 
+/**
+ * @brief Our implementation of GdkPixbufModuleStopLoadFunc, as
+ *   defined in gdk-pixbuf-io.h.
+ */
 static gboolean gdk_pixbuf_stop_load(gpointer data, GError **error) {
   SvgTinyContext* context = (SvgTinyContext*)data;
   GdkPixbuf* pixbuf = NULL;
-  gboolean result = TRUE;
   GError* sub_error = NULL;
 
+  g_assert(context != NULL);
+  if (context->svg_data == NULL || context->svg_data_size == 0) {
+    /* Is it possible to begin/stop with no increments in between?
+     * I sure don't know. Let's play it safe. */
+    return FALSE;
+  }
+
+  /* If we're inside of gtk-encode-symbolic-svg right now, we need to
+     process the insane librsvg-specific XInclude directive it hands
+     us before proceeding. */
+  gsize newsize;
+  gchar* newdata = process_gtk_symbolic_svg_xinclude(context->svg_data,
+                                                     context->svg_data_size,
+                                                     &newsize);
+  if (newdata != NULL) {
+    g_free(context->svg_data);
+    context->svg_data = newdata;
+    context->svg_data_size = newsize;
+  }
+
+  /* OK, we've got an SVG with no XIncludes now. */
   pixbuf = gdk_pixbuf_from_svg_buffer(context->svg_data,
-                                     context->svg_data_size,
-                                     &sub_error);
+                                      context->svg_data_size,
+                                      &sub_error);
 
   if (pixbuf != NULL) {
-    /*emit_size(context, pixbuf);*/
     emit_prepared(context, pixbuf);
     emit_updated(context, pixbuf);
     g_object_unref(pixbuf);
   }
   else {
     g_propagate_error(error, sub_error);
-    result = FALSE;
+    return FALSE;
   }
+  g_free(context->svg_data);
   g_free(context);
 
-  return result;
+  return TRUE;
 }
 
 
-G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module);
+/**
+ * @brief Our implementation of GdkPixbufModuleFillVtableFunc, as
+ *   defined in gdk-pixbuf-io.h.
+ */
 void fill_vtable(GdkPixbufModule* module) {
   module->begin_load = gdk_pixbuf_begin_load;
   module->load_increment = gdk_pixbuf_load_increment;
   module->stop_load = gdk_pixbuf_stop_load;
 }
 
-G_MODULE_EXPORT void fill_info(GdkPixbufFormat *info);
+/**
+ * @brief Our implementation of GdkPixbufModuleFillInfoFunc, as
+ *   defined in gdk-pixbuf-io.h.
+ */
 void fill_info(GdkPixbufFormat* info) {
   /* Borrowed from librsvg-2.40.21 */
   static const GdkPixbufModulePattern signature[] = {
@@ -475,17 +746,19 @@ void fill_info(GdkPixbufFormat* info) {
 }
 
 
+/**
+ * @brief Entry point of the svg2png test program.
+ */
 int main(int argc, char** argv) {
   char* svgpath;
   char* pngpath;
-  FILE* fp;
   GError* err = NULL;
   GdkPixbuf* pb;
 
   /* Parse arguments, and maybe print usage */
   if (argc < 3) {
-    printf("Usage: %s INPUT OUTPUT\n", argv[0]);
-    printf("Convert an SVG file (INPUT) to a PNG file (OUTPUT)\n");
+    g_printf("Usage: %s INPUT OUTPUT\n", argv[0]);
+    g_printf("Convert an SVG file (INPUT) to a PNG file (OUTPUT)\n");
     return 2;
   }
 
@@ -494,10 +767,10 @@ int main(int argc, char** argv) {
 
   pb = gdk_pixbuf_new_from_file(svgpath, &err);
   if (!pb) {
-    fprintf(stderr,
-            "Error %d in gdk_pixbuf_new_from_file: %s\n",
-            err->code,
-            err->message);
+    g_fprintf(stderr,
+              "Error %d in gdk_pixbuf_new_from_file: %s\n",
+              err->code,
+              err->message);
     g_error_free(err);
     return 1;
   }