X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=io-svg.c;h=8ef9ff90e3c1bfb73dcbd7e3bb919475d9495f4c;hb=HEAD;hp=c08fa7dfbd51998607752edbfdea31b424b488c7;hpb=bb68c95b5ddaf27bbde3491b89361d862a68635b;p=libsvgtiny-pixbuf.git diff --git a/io-svg.c b/io-svg.c index c08fa7d..8ef9ff9 100644 --- a/io-svg.c +++ b/io-svg.c @@ -1,39 +1,49 @@ -#include /* fprintf, printf */ -#include /* memcpy, memset, strstr */ +#include /* memcpy, memset */ #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 -/* The start of an XInclude that was inserted by - * gtk-encode-symbolic-svg */ -#define XI_SIGNATURE "= 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); + 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, @@ -165,11 +198,10 @@ static diagram_t* svgtiny_diagram_from_buffer(char* buffer, diagram->error_line, diagram->error_message); break; - } - return NULL; } - return diagram; + /* All other cases above are failure */ + return NULL; } /** @@ -182,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; @@ -194,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; } @@ -209,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; } @@ -251,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; } @@ -276,8 +308,8 @@ 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, +static GdkPixbuf* gdk_pixbuf_from_svg_buffer(const gchar* buffer, + gsize bytecount, GError** error) { diagram_t* diagram; cairo_t* cr = 0; @@ -331,6 +363,10 @@ static GdkPixbuf* gdk_pixbuf_from_svg_buffer(char* 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, @@ -339,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; @@ -350,14 +385,21 @@ 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, GError** error) { - size_t increment = 0; SvgTinyContext* context = (SvgTinyContext*)data; - /* YOLO, no error checking */ + 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); @@ -366,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, @@ -377,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); @@ -384,8 +434,153 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) { } + /** - * @brief Process certain elements in an SVG buffer. + * @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 @@ -400,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 * and tags, and then replace the original - * element with the result. Life is a little easier because the - * closing 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 tag intact. + * element with the result. * * @param buffer * A buffer containing SVG file data. @@ -419,73 +611,64 @@ static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) { * 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) { +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; } - 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. */ + 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; - guchar* decoded = g_base64_decode(xi, &decoded_size); - - /* We need another round of processing to strip the and - * 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 - * element so much, because if one exists, it has to come before the - * . As a result, we just need to strip everything up to the - * leading tag. */ - guchar* svg_open_start = strstr(decoded, ""); - memset(decoded, ' ', (1 + (svg_open_end - decoded))); - - /* We're going to keep everything up to xi_start. If the 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. */ - size_t newsize; - char* newdata = process_gtk_symbolic_svg_xinclude(context->svg_data, - context->svg_data_size, - &newsize); + 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; @@ -504,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[] = { @@ -557,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; } @@ -576,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; }