]> gitweb.michael.orlitzky.com - libsvgtiny-pixbuf.git/commitdiff
io-svg.c: initial support for gtk-encode-symbolic-svg
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 6 Aug 2023 14:38:32 +0000 (10:38 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 6 Aug 2023 14:38:32 +0000 (10:38 -0400)
There's some extra librsvg-specific magic we need to be able to handle
to support the gtk-encode-symbolic-svg utility. The result is still
incorrect (I don't think libsvgtiny supports <style> elements in an
SVG), but at least it's no longer outputting blank PNGs.

io-svg.c

index 543162f6c0b21f8357679f3e03ce76f408e91da9..c08fa7dfbd51998607752edbfdea31b424b488c7 100644 (file)
--- a/io-svg.c
+++ b/io-svg.c
@@ -1,5 +1,5 @@
 #include <stdio.h> /* fprintf, printf */
-#include <string.h> /* memcpy */
+#include <string.h> /* memcpy, memset, strstr */
 
 #include <cairo.h>
 #include <gdk/gdk.h>
 #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;
@@ -379,12 +384,115 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) {
 }
 
 
+/**
+ * @brief Process certain <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. 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.
+ *
+ * @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 char* process_gtk_symbolic_svg_xinclude(const char* buffer,
+                                              size_t buf_size,
+                                              size_t* new_size) {
+  char* xi_start;
+  char* xi;
+  char* xi_stop;
+
+  xi_start = strstr(buffer, XI_SIGNATURE);
+  if (xi_start == NULL) {
+    return NULL;
+  }
+
+  xi = xi_start + strlen(XI_SIGNATURE);
+  xi_stop = strstr(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. */
+    return NULL;
+  }
+
+  /* g_base64_decode needs a NULL-terminated string, so let's make
+     "xi" into one */
+  *xi_stop = 0;
+  gsize decoded_size;
+  guchar* decoded = g_base64_decode(xi, &decoded_size);
+
+  /* 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. */
+  guchar* svg_open_start = strstr(decoded, "<svg ");
+  guchar* svg_open_end = strstr(svg_open_start, ">");
+  memset(decoded, ' ', (1 + (svg_open_end - decoded)));
+
+  /* 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]. */
+  size_t keep_size = xi_start - buffer;
+  *new_size = keep_size + decoded_size;
+
+  char* result = g_malloc(*new_size);
+  memcpy(result, buffer, keep_size);
+  memcpy(result+keep_size, decoded, decoded_size);
+  g_free(decoded);
+  return result;
+}
+
+
 static gboolean gdk_pixbuf_stop_load(gpointer data, GError **error) {
   SvgTinyContext* context = (SvgTinyContext*)data;
   GdkPixbuf* pixbuf = NULL;
   gboolean result = TRUE;
   GError* sub_error = NULL;
 
+
+  /* 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. */
+  size_t newsize;
+  char* 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);