-#include <stdio.h> /* fopen, fprintf, fread, printf */
-#include <stdlib.h> /* malloc */
+#include <stdio.h> /* fprintf, printf */
+#include <string.h> /* memcpy */
#include <cairo.h>
#include <gdk/gdk.h>
-#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixbuf.h> /* includes glib.h */
#include <svgtiny.h>
/*
#define VIEWPORT_WIDTH 512
#define VIEWPORT_HEIGHT 512
+#define SVG_BUFFER_INCREMENT (size_t)4194304
+
/* Convenient typedefs for libsvgtiny */
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 "file" */
+ char* svg_data;
+ size_t svg_data_size;
+ size_t svg_data_max;
+
+} SvgTinyContext;
+
+
/**
* @brief Render an svgtiny path using cairo.
*
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,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_FAILED,
+ "svgtiny_create() failed");
return NULL;
}
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,
+ "libdom error 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,
+ "encountered svgtiny_NOT_SVG 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 error in svgtiny_parse() on line %i: %s",
+ diagram->error_line,
+ diagram->error_message);
break;
}
- fprintf(stderr, "\n");
return NULL;
}
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;
}
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;
}
/* 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);
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;
}
g_set_error_literal(error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
- "Could not create Cairo surface from SVG diagram");
+ "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);
+ 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");
+ "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;
+
+ /* YOLO, no error checking */
+ context->svg_data = g_malloc(SVG_BUFFER_INCREMENT);
+ context->svg_data_size = 0;
+
+ return context;
+}
+
+static gboolean gdk_pixbuf_load_increment(gpointer data,
+ const guchar* buf,
+ guint size,
+ GError** error) {
+ size_t increment = 0;
+ SvgTinyContext* context = (SvgTinyContext*)data;
+
+ if (context->svg_data_size + size > context->svg_data_max) {
+ if (size > SVG_BUFFER_INCREMENT) {
+ increment = size;
+ }
+ else {
+ increment = SVG_BUFFER_INCREMENT;
+ }
+
+ /* YOLO, no error checking */
+ context->svg_data = g_realloc(context->svg_data,
+ context->svg_data_max + increment);
+
+ context->svg_data_max += increment;
+ }
+
+ memcpy(context->svg_data + context->svg_data_size, buf, size);
+ context->svg_data_size += 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 void emit_size(SvgTinyContext* context, GdkPixbuf* pixbuf) {
+ int w = gdk_pixbuf_get_width(pixbuf);
+ int h = gdk_pixbuf_get_height(pixbuf);
+ if (context->size_func != NULL) {
+ (*context->size_func)(&w, &h, 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_size(context, pixbuf);*/
+ emit_prepared(context, pixbuf);
+ emit_updated(context, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ else {
+ g_propagate_error(error, sub_error);
+ result = FALSE;
+ }
+ 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);
};
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";
}
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;
}