X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=io-svg.c;h=8ef9ff90e3c1bfb73dcbd7e3bb919475d9495f4c;hb=HEAD;hp=13b692b2be1a93a68b59ae59228bdd4065032440;hpb=7e64f748cdf6cc7068fec1b72780abc2d0a94548;p=libsvgtiny-pixbuf.git diff --git a/io-svg.c b/io-svg.c index 13b692b..8ef9ff9 100644 --- a/io-svg.c +++ b/io-svg.c @@ -1,18 +1,29 @@ -#include /* fopen, fprintf, fread, printf */ -#include /* malloc */ +#include /* memcpy, memset */ #include -#include -#include +#include /* includes glib.h */ +#include +#include +#include #include -/* - * 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 @@ -21,9 +32,33 @@ 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; + 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. * @@ -39,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: @@ -49,116 +84,124 @@ static void render_path(cairo_t* cr, shape_t* path) { 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 element in svgtiny_parse()"); break; case svgtiny_SVG_ERROR: - fprintf(stderr, "svgtiny_SVG_ERROR: line %i: %s", - diagram->error_line, - diagram->error_message); - break; - default: - fprintf(stderr, "unknown svgtiny_code %i", code); + 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; - } - fprintf(stderr, "\n"); - return NULL; } - return diagram; + /* All other cases above are failure */ + return NULL; } /** @@ -171,21 +214,21 @@ static diagram_t* svgtiny_diagram_from_file(FILE* fp, int width, int height) { * 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; } @@ -198,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; } @@ -219,17 +262,17 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { /* 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); @@ -240,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; } @@ -250,17 +293,36 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { 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; } @@ -268,47 +330,386 @@ static GdkPixbuf* gdk_pixbuf_from_svg_file_stream(FILE *fp, GError** error) { 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; } -G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module); +/** + * @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_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 buf_size, + GError** error) { + SvgTinyContext* context = (SvgTinyContext*)data; + + 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); + } +} + +/** + * @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 element by its data. + * + * @param node + * A pointer to an 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 elements in a tree by their data. + * + * @param node + * A node pointer, to the root of the tree. + * + * @return TRUE if we replaced any 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 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, + * + * + * + * 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 + * and tags, and then replace the original + * 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 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; + 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); + emit_updated(context, pixbuf); + g_object_unref(pixbuf); + } + else { + g_propagate_error(error, sub_error); + return FALSE; + } + g_free(context->svg_data); + g_free(context); + + return TRUE; +} + + +/** + * @brief Our implementation of GdkPixbufModuleFillVtableFunc, as + * defined in gdk-pixbuf-io.h. + */ void fill_vtable(GdkPixbufModule* module) { - module->load = gdk_pixbuf_from_svg_file_stream; + 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[] = { @@ -336,44 +737,40 @@ void fill_info(GdkPixbufFormat* info) { }; 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; }