]> 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 b1cb332f1aca74a059fea117f42eb91dcd126867..8ef9ff90e3c1bfb73dcbd7e3bb919475d9495f4c 100644 (file)
--- a/io-svg.c
+++ b/io-svg.c
@@ -1,35 +1,45 @@
 #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
 
-/* The start of an XInclude that was inserted by
- * gtk-encode-symbolic-svg */
-#define XI_SIGNATURE "<xi:include href=\"data:text/xml;base64,"
-
-
 /* 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 SVG "file" that we're building in memory. */
@@ -46,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.
  *
@@ -118,7 +131,7 @@ 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(gchar* buffer,
+static diagram_t* svgtiny_diagram_from_buffer(const gchar* buffer,
                                               gsize bytecount,
                                               guint width,
                                               guint height,
@@ -137,11 +150,23 @@ static diagram_t* svgtiny_diagram_from_buffer(gchar* buffer,
 
   g_assert((int)width >= 0);
   g_assert((int)height >= 0);
+
+  /* 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);
+                       buffer,
+                       bytecount, "",
+                       (int)width,
+                       (int)height);
+  g_mutex_unlock (&mutex);
 
   switch(code) {
     case svgtiny_OK:
@@ -189,7 +214,7 @@ static diagram_t* svgtiny_diagram_from_buffer(gchar* 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;
@@ -283,7 +308,7 @@ 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(gchar* buffer,
+static GdkPixbuf* gdk_pixbuf_from_svg_buffer(const gchar* buffer,
                                              gsize bytecount,
                                              GError** error) {
   diagram_t* diagram;
@@ -338,6 +363,10 @@ static GdkPixbuf* gdk_pixbuf_from_svg_buffer(gchar* buffer,
 }
 
 
+/**
+ * @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,
@@ -346,7 +375,6 @@ static gpointer gdk_pixbuf_begin_load(GdkPixbufModuleSizeFunc size_func,
 
   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;
@@ -357,6 +385,11 @@ static gpointer gdk_pixbuf_begin_load(GdkPixbufModuleSizeFunc size_func,
   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 buf_size,
@@ -375,6 +408,10 @@ static gboolean gdk_pixbuf_load_increment(gpointer data,
   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,
@@ -386,6 +423,10 @@ static void emit_updated(SvgTinyContext* context, GdkPixbuf* pixbuf) {
   }
 }
 
+/**
+ * @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);
@@ -393,8 +434,153 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
 }
 
 
+
 /**
- * @brief Process certain <xi:include> elements in an SVG buffer.
+ * @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
@@ -409,10 +595,7 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
  * 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. Life is a little easier because the
- * closing </svg> tag always immediately follows the XInclude; we can
- * chop the whole thing off, decode the base64 stuff, and then paste
- * the result back on the end with its own closing </svg> tag intact.
+ * element with the result.
  *
  * @param buffer
  *   A buffer containing SVG file data.
@@ -430,83 +613,54 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
  */
 static gchar* process_gtk_symbolic_svg_xinclude(const gchar* buffer,
                                                 gsize buf_size,
-                                               gsize* new_size) {
-  gchar* xi_start;
-  gchar* xi;
-  gchar* xi_stop;
+                                                gsize* new_size) {
 
-  xi_start = g_strstr_len(buffer, buf_size, XI_SIGNATURE);
-  if (xi_start == NULL) {
+  xmlDoc* doc = xmlReadMemory(buffer,buf_size,"symbolic.xml",NULL,0);
+  if (doc == NULL) {
     return NULL;
   }
 
-  xi = xi_start + strlen(XI_SIGNATURE);
-  xi_stop = g_strstr_len(xi, (buffer + buf_size) - xi, "\"");
-  if(xi_stop == NULL) {
-    /* We found the start of an XInclude, but not the end of its
-       base64-encoded data? Play it safe and do nothing. */
+  xmlNode* root_element = xmlDocGetRootElement(doc);
+  if (root_element == NULL) {
     return NULL;
   }
 
-  /* g_base64_decode needs a NULL-terminated string, so let's make
-     "xi" into one */
-  *xi_stop = 0;
-  gsize decoded_size;
-
-  /* All files are ASCII, right? In GTK's gdkpixbufutils.c, our base64
-   * data is encoded from a binary file stream, i.e. bytes, without
-   * any regard for what the text inside represents. Elsewhere we are
-   * pretending that gchar is a reasonable data type to use for the
-   * contents of an SVG file; here we are saying that again out loud.
-   */
-  gchar* decoded = (gchar*)g_base64_decode(xi, &decoded_size);
-  *xi_stop = '"';
-
-  /* We need another round of processing to strip the <xml> and <svg>
-   * elements out of "decoded", but it's simpler to just overwrite
-   * them with spaces before we proceed. We'll wind up with a document
-   * that has a conspicuous chunk of whitespace in the middle of it,
-   * but whatever. Note that we don't need to worry about the <xml>
-   * element so much, because if one exists, it has to come before the
-   * <svg>. As a result, we just need to strip everything up to the
-   * leading <svg> tag. */
-  gchar* svg_open_start = g_strstr_len(decoded, decoded_size, "<svg ");
-  if (svg_open_start == NULL) {
-    /* The decoded data is not what we were expecting, Give up. */
-    g_free(decoded);
-    return NULL;
-  }
-  else {
-    gchar* svg_open_end = g_strstr_len(svg_open_start, decoded_size, ">");
-    if (svg_open_end == NULL) {
-      /* The decoded data is not what we were expecting. Give up. */
-      g_free(decoded);
-      return NULL;
-    }
-    memset(decoded, ' ', (1 + (svg_open_end - decoded)));
+  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;
   }
 
-  /* We're going to keep everything up to xi_start. If the <xi:include
-   * started at, say, position three, then this would compute a size
-   * of three. Which is correct: we want to retain buffer[0],
-   * buffer[1], and buffer[2]. */
-  gsize keep_size = xi_start - buffer;
-  *new_size = keep_size + decoded_size;
-
-  gchar* result = g_malloc(*new_size);
-  memcpy(result, buffer, keep_size);
-  memcpy(result+keep_size, decoded, decoded_size);
-  g_free(decoded);
+  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
@@ -533,23 +687,29 @@ static gboolean gdk_pixbuf_stop_load(gpointer data, GError **error) {
   }
   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[] = {
@@ -586,6 +746,9 @@ void fill_info(GdkPixbufFormat* info) {
 }
 
 
+/**
+ * @brief Entry point of the svg2png test program.
+ */
 int main(int argc, char** argv) {
   char* svgpath;
   char* pngpath;