X-Git-Url: https://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=io-svg.c;h=543162f6c0b21f8357679f3e03ce76f408e91da9;hb=96deefceb53d73852b56ca0c71c08ba9abc758ca;hp=13b692b2be1a93a68b59ae59228bdd4065032440;hpb=7e64f748cdf6cc7068fec1b72780abc2d0a94548;p=libsvgtiny-pixbuf.git diff --git a/io-svg.c b/io-svg.c index 13b692b..543162f 100644 --- a/io-svg.c +++ b/io-svg.c @@ -1,9 +1,9 @@ -#include /* fopen, fprintf, fread, printf */ -#include /* malloc */ +#include /* fprintf, printf */ +#include /* memcpy */ #include #include -#include +#include /* includes glib.h */ #include /* @@ -21,6 +21,23 @@ typedef struct svgtiny_diagram diagram_t; typedef struct svgtiny_shape shape_t; +typedef struct { + GdkPixbufModuleUpdatedFunc updated_func; + GdkPixbufModulePreparedFunc prepared_func; + GdkPixbufModuleSizeFunc size_func; + gpointer user_data; + + /* The SVG "file" that we're building in memory. */ + char* 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. * @@ -39,8 +56,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,112 +66,101 @@ 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 fp - * A pointer to an open file stream. + * @param buffer + * The buffer containing the SVG document. + * + * @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(char* buffer, + size_t bytecount, + int width, + int 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); if (code != svgtiny_OK) { - fprintf(stderr, "svgtiny_parse failed with "); switch (code) { 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; } @@ -178,14 +184,14 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { 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)); + "cairo_image_surface_create failed: %s\n", + cairo_status_to_string(crs)); cairo_surface_destroy(surface); return NULL; } @@ -199,8 +205,8 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { if (crs != CAIRO_STATUS_SUCCESS) { fprintf(stderr, - "cairo_create failed: %s\n", - cairo_status_to_string(crs)); + "cairo_create failed: %s\n", + cairo_status_to_string(crs)); cairo_destroy(cr); return NULL; } @@ -219,17 +225,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); @@ -241,8 +247,8 @@ static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { crs = cairo_status(cr); if (crs != CAIRO_STATUS_SUCCESS) { fprintf(stderr, - "cairo error: %s\n", - cairo_status_to_string(crs)); + "cairo error: %s\n", + cairo_status_to_string(crs)); cairo_destroy(cr); return NULL; } @@ -250,17 +256,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(char* buffer, + size_t 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,44 +293,123 @@ 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; } +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->size_func = size_func; + 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; +} + +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 */ + 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; +} + +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); + } +} + +static void emit_prepared(SvgTinyContext* context, GdkPixbuf* pixbuf) { + if (context->prepared_func != NULL) { + (*context->prepared_func)(pixbuf, NULL, context->user_data); + } +} + + +static gboolean gdk_pixbuf_stop_load(gpointer data, GError **error) { + SvgTinyContext* context = (SvgTinyContext*)data; + GdkPixbuf* pixbuf = NULL; + gboolean result = TRUE; + GError* sub_error = NULL; + + 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); + result = FALSE; + } + g_free(context->svg_data); + g_free(context); + + return result; +} + + G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module); 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); @@ -336,10 +440,10 @@ 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"; } @@ -362,18 +466,12 @@ int main(int argc, char** argv) { 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); + "Error %d in gdk_pixbuf_new_from_file: %s\n", + err->code, + err->message); g_error_free(err); return 1; }