-#include <stdio.h> /* fopen, fprintf, fread, printf */
-#include <stdlib.h> /* malloc */
+#include <string.h> /* memcpy, memset */
#include <cairo.h>
-#include <gdk/gdk.h>
-#include <gdk-pixbuf/gdk-pixbuf.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
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. */
+ 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;
/**
* @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.
*
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:
break;
case svgtiny_PATH_LINE:
cairo_line_to(cr,
- path->path[j + 1],
- path->path[j + 2]);
+ path->path[j + 1],
+ path->path[j + 2]);
j += 3;
break;
case svgtiny_PATH_BEZIER:
cairo_curve_to(cr,
- path->path[j + 1],
- path->path[j + 2],
- path->path[j + 3],
- path->path[j + 4],
- path->path[j + 5],
- path->path[j + 6]);
+ path->path[j + 1],
+ path->path[j + 2],
+ path->path[j + 3],
+ path->path[j + 4],
+ path->path[j + 5],
+ path->path[j + 6]);
j += 7;
- break;
- default:
- fprintf(stderr, "error: unmatched case in render_path\n");
- j += 1;
}
}
if (path->fill != svgtiny_TRANSPARENT) {
cairo_set_source_rgba(cr,
- svgtiny_RED(path->fill) / 255.0,
- svgtiny_GREEN(path->fill) / 255.0,
- svgtiny_BLUE(path->fill) / 255.0,
- 1);
+ svgtiny_RED(path->fill) / 255.0,
+ svgtiny_GREEN(path->fill) / 255.0,
+ svgtiny_BLUE(path->fill) / 255.0,
+ 1);
cairo_fill_preserve(cr);
}
if (path->stroke != svgtiny_TRANSPARENT) {
cairo_set_source_rgba(cr,
- svgtiny_RED(path->stroke) / 255.0,
- svgtiny_GREEN(path->stroke) / 255.0,
- svgtiny_BLUE(path->stroke) / 255.0,
- 1);
+ svgtiny_RED(path->stroke) / 255.0,
+ svgtiny_GREEN(path->stroke) / 255.0,
+ svgtiny_BLUE(path->stroke) / 255.0,
+ 1);
cairo_set_line_width(cr, path->stroke_width);
cairo_stroke_preserve(cr);
}
}
/**
- * @brief Parse an SVG file into a diagram_t structure.
+ * @brief Parse a buffer of SVG data into a diagram_t structure.
+ *
+ * @param buffer
+ * The buffer containing the SVG document.
*
- * @param fp
- * A pointer to an open file stream.
+ * @param bytecount
+ * The number of bytes in @c buffer.
*
* @return If successful, a pointer to a @c diagram_t structure is
* 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_file(FILE* fp, int width, int height) {
+static diagram_t* svgtiny_diagram_from_buffer(const gchar* buffer,
+ gsize bytecount,
+ guint width,
+ guint height,
+ GError** error) {
diagram_t* diagram;
-
- size_t bytecount;
- char* buffer;
- size_t bytesread;
svgtiny_code code;
- /* Find the size of the file stream */
- fseek(fp, 0L, SEEK_END);
- bytecount = ftell(fp);
- rewind(fp);
-
- buffer = malloc(bytecount);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %zd bytes\n", bytecount);
- return NULL;
- }
-
- bytesread = fread(buffer, 1, bytecount, fp);
- if (bytesread != bytecount) {
- fprintf(stderr, "Read only %zd of %zd bytes from stream\n",
- bytesread,
- bytecount);
- }
- fclose(fp);
-
diagram = svgtiny_create();
if (!diagram) {
- fprintf(stderr, "svgtiny_create() failed\n");
+ g_set_error_literal(error,
+ 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) {
- fprintf(stderr, "svgtiny_parse failed with ");
- 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:
- fprintf(stderr, "svgtiny_OUT_OF_MEMORY");
+ g_set_error_literal(error,
+ G_FILE_ERROR,
+ G_FILE_ERROR_NOMEM,
+ "out of memory in svgtiny_parse()");
break;
case svgtiny_LIBDOM_ERROR:
- fprintf(stderr, "svgtiny_LIBDOM_ERROR");
+ g_set_error_literal(error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+ "invalid XML DOM in svgtiny_parse()");
break;
case svgtiny_NOT_SVG:
- fprintf(stderr, "svgtiny_NOT_SVG");
+ g_set_error_literal(error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+ "missing <svg> element in svgtiny_parse()");
break;
case svgtiny_SVG_ERROR:
- fprintf(stderr, "svgtiny_SVG_ERROR: line %i: %s",
- diagram->error_line,
- diagram->error_message);
+ g_set_error(error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+ "SVG format error in svgtiny_parse() on line %i: %s",
+ diagram->error_line,
+ diagram->error_message);
break;
- default:
- fprintf(stderr, "unknown svgtiny_code %i", code);
- break;
- }
- fprintf(stderr, "\n");
- return NULL;
}
- return diagram;
+ /* All other cases above are failure */
+ return NULL;
}
/**
* 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;
unsigned int i;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
- diagram->width,
- diagram->height);
+ diagram->width,
+ diagram->height);
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;
}
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;
}
/* If this shape is text... */
if (diagram->shape[i].text) {
/* Figure out what color to use from the R/G/B components of the
- shape's stroke. */
+ shape's stroke. */
cairo_set_source_rgba(cr,
- svgtiny_RED(diagram->shape[i].stroke) / 255.0,
- svgtiny_GREEN(diagram->shape[i].stroke) / 255.0,
- svgtiny_BLUE(diagram->shape[i].stroke) / 255.0,
- 1);
+ svgtiny_RED(diagram->shape[i].stroke) / 255.0,
+ svgtiny_GREEN(diagram->shape[i].stroke) / 255.0,
+ svgtiny_BLUE(diagram->shape[i].stroke) / 255.0,
+ 1);
/* Then move to the actual position of the text within the
- shape... */
+ shape... */
cairo_move_to(cr,
- diagram->shape[i].text_x,
- diagram->shape[i].text_y);
+ diagram->shape[i].text_x,
+ diagram->shape[i].text_y);
/* and draw it. */
cairo_show_text(cr, diagram->shape[i].text);
/* 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;
}
return cr;
}
-static GdkPixbuf* gdk_pixbuf_from_svg_file_stream(FILE *fp, GError** error) {
+/**
+ * @brief Create a GdkPixbuf from a buffer of SVG data.
+ *
+ * @param buffer
+ * The buffer containing the SVG document.
+ *
+ * @param bytecount
+ * The number of bytes in @c buffer.
+ *
+ * @param error
+ * The address of a @c GError pointer that we use to return errors.
+ *
+ * @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(const gchar* buffer,
+ gsize bytecount,
+ GError** error) {
diagram_t* diagram;
cairo_t* cr = 0;
GdkPixbuf* pb;
+ GError* sub_error = NULL;
- diagram = svgtiny_diagram_from_file(fp, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
+ diagram = svgtiny_diagram_from_buffer(buffer,
+ bytecount,
+ VIEWPORT_WIDTH,
+ VIEWPORT_HEIGHT,
+ &sub_error);
if (!diagram) {
- g_set_error_literal(error,
- GDK_PIXBUF_ERROR,
- GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
- "Could not parse SVG diagram from file");
+ g_propagate_error(error, sub_error);
return NULL;
}
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)
-{
- SvgTinyContext* context = g_new0(SvgTinyContext, 1);
-
- context->size_func = size_func;
- context->prepared_func = prep_func;
- context->updated_func = updated_func;
- context->user_data = user_data;
-
- return context;
+ GdkPixbufModulePreparedFunc prep_func,
+ GdkPixbufModuleUpdatedFunc updated_func,
+ gpointer user_data,
+ GError **error) {
+
+ SvgTinyContext* context = g_new(SvgTinyContext, 1);
+
+ context->prepared_func = prep_func;
+ context->updated_func = updated_func;
+ context->user_data = user_data;
+
+ 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) {
+ const guchar* buf,
+ guint buf_size,
+ GError** error) {
SvgTinyContext* context = (SvgTinyContext*)data;
- /*
- if (!rsvg_handle_write (context->handle, buf, size, error)) {
- rsvg_propagate_error (error, _("Error writing"), ERROR_WRITING);
- return FALSE;
+
+ if (buf_size == 0) {
+ return TRUE;
}
- */
+
+ 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);
}
}
+
+
+/**
+ * @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);
if (pixbuf != NULL) {
emit_prepared(context, pixbuf);
g_object_unref(pixbuf);
}
else {
- result = FALSE;
+ g_propagate_error(error, sub_error);
+ return FALSE;
}
- g_free (context);
+ 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;
- module->load = gdk_pixbuf_from_svg_file_stream;
}
-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[] = {
};
info->name = "svg";
- info->signature = (GdkPixbufModulePattern*) signature;
+ info->signature = (GdkPixbufModulePattern*)signature;
info->description = "Scalable Vector Graphics";
- info->mime_types = (gchar**) mime_types;
- info->extensions = (gchar**) extensions;
+ info->mime_types = (gchar**)mime_types;
+ info->extensions = (gchar**)extensions;
info->flags = GDK_PIXBUF_FORMAT_SCALABLE;
info->license = "AGPL3";
}
+/**
+ * @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;
}
svgpath = argv[1];
pngpath = argv[2];
- fp = fopen(svgpath, "rb");
- if (!fp) {
- perror(svgpath);
- return 1;
- }
-
- pb = gdk_pixbuf_from_svg_file_stream(fp, &err);
+ pb = gdk_pixbuf_new_from_file(svgpath, &err);
if (!pb) {
- fprintf(stderr,
- "Error %d in gdk_pixbuf_from_svg_file_stream: %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;
}