]> gitweb.michael.orlitzky.com - libsvgtiny.git/commitdiff
SVG code extracted from NetSurf into a standalone library.
authorJames Bursa <james@netsurf-browser.org>
Sat, 2 Feb 2008 17:47:18 +0000 (17:47 -0000)
committerJames Bursa <james@netsurf-browser.org>
Sat, 2 Feb 2008 17:47:18 +0000 (17:47 -0000)
svn path=/trunk/libsvgtiny/; revision=3824

colors.gperf [new file with mode: 0644]
makefile [new file with mode: 0644]
svgtiny.c [new file with mode: 0644]
svgtiny.h [new file with mode: 0644]
svgtiny_display [new file with mode: 0755]
svgtiny_test.c [new file with mode: 0644]

diff --git a/colors.gperf b/colors.gperf
new file mode 100644 (file)
index 0000000..ae05e60
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2008 James Bursa <james@semichrome.net>
+ */
+
+%language=ANSI-C
+%struct-type
+%switch=1
+%define hash-function-name svgtiny_color_hash
+%define lookup-function-name svgtiny_color_lookup
+%readonly-tables
+
+%{
+#include <string.h>
+#include "svgtiny.h"
+%}
+
+struct svgtiny_named_color;
+%%
+aliceblue,     svgtiny_RGB(240, 248, 255)
+antiquewhite,  svgtiny_RGB(250, 235, 215)
+aqua,          svgtiny_RGB(  0, 255, 255)
+aquamarine,    svgtiny_RGB(127, 255, 212)
+azure,         svgtiny_RGB(240, 255, 255)
+beige,         svgtiny_RGB(245, 245, 220)
+bisque,                svgtiny_RGB(255, 228, 196)
+black,         svgtiny_RGB(  0,   0,   0)
+blanchedalmond,        svgtiny_RGB(255, 235, 205)
+blue,          svgtiny_RGB(  0,   0, 255)
+blueviolet,    svgtiny_RGB(138,  43, 226)
+brown,         svgtiny_RGB(165,  42,  42)
+burlywood,     svgtiny_RGB(222, 184, 135)
+cadetblue,     svgtiny_RGB( 95, 158, 160)
+chartreuse,    svgtiny_RGB(127, 255,   0)
+chocolate,     svgtiny_RGB(210, 105,  30)
+coral,         svgtiny_RGB(255, 127,  80)
+cornflowerblue,        svgtiny_RGB(100, 149, 237)
+cornsilk,      svgtiny_RGB(255, 248, 220)
+crimson,       svgtiny_RGB(220,  20,  60)
+cyan,          svgtiny_RGB(  0, 255, 255)
+darkblue,      svgtiny_RGB(  0,   0, 139)
+darkcyan,      svgtiny_RGB(  0, 139, 139)
+darkgoldenrod, svgtiny_RGB(184, 134,  11)
+darkgray,      svgtiny_RGB(169, 169, 169)
+darkgreen,     svgtiny_RGB(  0, 100,   0)
+darkgrey,      svgtiny_RGB(169, 169, 169)
+darkkhaki,     svgtiny_RGB(189, 183, 107)
+darkmagenta,   svgtiny_RGB(139, 0,   139)
+darkolivegreen,        svgtiny_RGB( 85, 107,  47)
+darkorange,    svgtiny_RGB(255, 140,   0)
+darkorchid,    svgtiny_RGB(153,  50, 204)
+darkred,       svgtiny_RGB(139,   0,   0)
+darksalmon,    svgtiny_RGB(233, 150, 122)
+darkseagreen,  svgtiny_RGB(143, 188, 143)
+darkslateblue, svgtiny_RGB( 72,  61, 139)
+darkslategray, svgtiny_RGB( 47,  79,  79)
+darkslategrey, svgtiny_RGB( 47,  79,  79)
+darkturquoise, svgtiny_RGB(  0, 206, 209)
+darkviolet,    svgtiny_RGB(148,   0, 211)
+deeppink,      svgtiny_RGB(255,  20, 147)
+deepskyblue,   svgtiny_RGB(  0, 191, 255)
+dimgray,       svgtiny_RGB(105, 105, 105)
+dimgrey,       svgtiny_RGB(105, 105, 105)
+dodgerblue,    svgtiny_RGB( 30, 144, 255)
+firebrick,     svgtiny_RGB(178,  34,  34)
+floralwhite,   svgtiny_RGB(255, 250, 240)
+forestgreen,   svgtiny_RGB( 34, 139,  34)
+fuchsia,       svgtiny_RGB(255,   0, 255)
+gainsboro,     svgtiny_RGB(220, 220, 220)
+ghostwhite,    svgtiny_RGB(248, 248, 255)
+gold,          svgtiny_RGB(255, 215,   0)
+goldenrod,     svgtiny_RGB(218, 165,  32)
+gray,          svgtiny_RGB(128, 128, 128)
+grey,          svgtiny_RGB(128, 128, 128)
+green,         svgtiny_RGB(  0, 128,   0)
+greenyellow,   svgtiny_RGB(173, 255,  47)
+honeydew,      svgtiny_RGB(240, 255, 240)
+hotpink,       svgtiny_RGB(255, 105, 180)
+indianred,     svgtiny_RGB(205,  92,  92)
+indigo,                svgtiny_RGB( 75,   0, 130)
+ivory,         svgtiny_RGB(255, 255, 240)
+khaki,         svgtiny_RGB(240, 230, 140)
+lavender,      svgtiny_RGB(230, 230, 250)
+lavenderblush, svgtiny_RGB(255, 240, 245)
+lawngreen,     svgtiny_RGB(124, 252,   0)
+lemonchiffon,  svgtiny_RGB(255, 250, 205)
+lightblue,     svgtiny_RGB(173, 216, 230)
+lightcoral,    svgtiny_RGB(240, 128, 128)
+lightcyan,     svgtiny_RGB(224, 255, 255)
+lightgoldenrodyellow,  svgtiny_RGB(250, 250, 210)
+lightgray,     svgtiny_RGB(211, 211, 211)
+lightgreen,    svgtiny_RGB(144, 238, 144)
+lightgrey,     svgtiny_RGB(211, 211, 211)
+lightpink,     svgtiny_RGB(255, 182, 193)
+lightsalmon,   svgtiny_RGB(255, 160, 122)
+lightseagreen, svgtiny_RGB( 32, 178, 170)
+lightskyblue,  svgtiny_RGB(135, 206, 250)
+lightslategray,        svgtiny_RGB(119, 136, 153)
+lightslategrey,        svgtiny_RGB(119, 136, 153)
+lightsteelblue,        svgtiny_RGB(176, 196, 222)
+lightyellow,   svgtiny_RGB(255, 255, 224)
+lime,          svgtiny_RGB(  0, 255,   0)
+limegreen,     svgtiny_RGB( 50, 205,  50)
+linen,         svgtiny_RGB(250, 240, 230)
+magenta,       svgtiny_RGB(255,   0, 255)
+maroon,                svgtiny_RGB(128,   0,   0)
+mediumaquamarine,      svgtiny_RGB(102, 205, 170)
+mediumblue,    svgtiny_RGB(  0,   0, 205)
+mediumorchid,  svgtiny_RGB(186,  85, 211)
+mediumpurple,  svgtiny_RGB(147, 112, 219)
+mediumseagreen,        svgtiny_RGB( 60, 179, 113)
+mediumslateblue,       svgtiny_RGB(123, 104, 238)
+mediumspringgreen,     svgtiny_RGB(  0, 250, 154)
+mediumturquoise,       svgtiny_RGB( 72, 209, 204)
+mediumvioletred,       svgtiny_RGB(199,  21, 133)
+midnightblue,  svgtiny_RGB( 25,  25, 112)
+mintcream,     svgtiny_RGB(245, 255, 250)
+mistyrose,     svgtiny_RGB(255, 228, 225)
+moccasin,      svgtiny_RGB(255, 228, 181)
+navajowhite,   svgtiny_RGB(255, 222, 173)
+navy,          svgtiny_RGB(  0,   0, 128)
+oldlace,       svgtiny_RGB(253, 245, 230)
+olive,         svgtiny_RGB(128, 128,   0)
+olivedrab,     svgtiny_RGB(107, 142,  35)
+orange,                svgtiny_RGB(255, 165,   0)
+orangered,     svgtiny_RGB(255,  69,   0)
+orchid,                svgtiny_RGB(218, 112, 214)
+palegoldenrod, svgtiny_RGB(238, 232, 170)
+palegreen,     svgtiny_RGB(152, 251, 152)
+paleturquoise, svgtiny_RGB(175, 238, 238)
+palevioletred, svgtiny_RGB(219, 112, 147)
+papayawhip,    svgtiny_RGB(255, 239, 213)
+peachpuff,     svgtiny_RGB(255, 218, 185)
+peru,          svgtiny_RGB(205, 133,  63)
+pink,          svgtiny_RGB(255, 192, 203)
+plum,          svgtiny_RGB(221, 160, 221)
+powderblue,    svgtiny_RGB(176, 224, 230)
+purple,                svgtiny_RGB(128,   0, 128)
+red,           svgtiny_RGB(255,   0,   0)
+rosybrown,     svgtiny_RGB(188, 143, 143)
+royalblue,     svgtiny_RGB( 65, 105, 225)
+saddlebrown,   svgtiny_RGB(139,  69,  19)
+salmon,                svgtiny_RGB(250, 128, 114)
+sandybrown,    svgtiny_RGB(244, 164,  96)
+seagreen,      svgtiny_RGB( 46, 139,  87)
+seashell,      svgtiny_RGB(255, 245, 238)
+sienna,                svgtiny_RGB(160,  82,  45)
+silver,                svgtiny_RGB(192, 192, 192)
+skyblue,       svgtiny_RGB(135, 206, 235)
+slateblue,     svgtiny_RGB(106,  90, 205)
+slategray,     svgtiny_RGB(112, 128, 144)
+slategrey,     svgtiny_RGB(112, 128, 144)
+snow,          svgtiny_RGB(255, 250, 250)
+springgreen,   svgtiny_RGB(  0, 255, 127)
+steelblue,     svgtiny_RGB( 70, 130, 180)
+tan,           svgtiny_RGB(210, 180, 140)
+teal,          svgtiny_RGB(  0, 128, 128)
+thistle,       svgtiny_RGB(216, 191, 216)
+tomato,                svgtiny_RGB(255,  99,  71)
+turquoise,     svgtiny_RGB( 64, 224, 208)
+violet,                svgtiny_RGB(238, 130, 238)
+wheat,         svgtiny_RGB(245, 222, 179)
+white,         svgtiny_RGB(255, 255, 255)
+whitesmoke,    svgtiny_RGB(245, 245, 245)
+yellow,                svgtiny_RGB(255, 255,   0)
+yellowgreen,   svgtiny_RGB(154, 205,  50)
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..ef3d27c
--- /dev/null
+++ b/makefile
@@ -0,0 +1,38 @@
+#
+# This file is part of Libsvgtiny
+# Licensed under the MIT License,
+#                http://opensource.org/licenses/mit-license.php
+# Copyright 2008 James Bursa <james@semichrome.net>
+#
+
+SOURCE = svgtiny.c colors.c
+HDRS = svgtiny.h
+
+.PHONY: all install clean
+
+CFLAGS = -std=c99 -g -W -Wall -Wundef -Wpointer-arith -Wcast-qual \
+       -Wcast-align -Wwrite-strings -Wstrict-prototypes \
+       -Wmissing-prototypes -Wmissing-declarations \
+       -Wnested-externs -Winline -Wno-cast-align \
+       `xml2-config --cflags`
+LIBS = `xml2-config --libs`
+ARFLAGS = cr
+
+OBJS = $(SOURCE:.c=.o)
+
+all: libsvgtiny.a svgtiny_test$(EXEEXT)
+
+libsvgtiny.a: $(OBJS)
+       $(AR) $(ARFLAGS) $@ $(OBJS)
+
+svgtiny_test$(EXEEXT): svgtiny_test.c libsvgtiny.a
+       $(CC) $(CFLAGS) $(LIBS) -o $@ $^
+
+clean:
+       -rm *.o libsvgtiny.a svgtiny_test$(EXEEXT)
+
+colors.c: colors.gperf
+       gperf --output-file=$@ $<
+
+.c.o: $(HDRS)
+       $(CC) $(CFLAGS) -c -o $@ $<
diff --git a/svgtiny.c b/svgtiny.c
new file mode 100644 (file)
index 0000000..21a23b8
--- /dev/null
+++ b/svgtiny.c
@@ -0,0 +1,1074 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2008 James Bursa <james@semichrome.net>
+ */
+
+#define _GNU_SOURCE  /* for strndup */
+#include <assert.h>
+#include <math.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libxml/parser.h>
+#include <libxml/debugXML.h>
+#include "svgtiny.h"
+
+
+struct svgtiny_parse_state {
+       struct svgtiny_diagram *diagram;
+       xmlDoc *document;
+
+       float viewport_width;
+       float viewport_height;
+
+       /* current transformation matrix */
+       struct {
+               float a, b, c, d, e, f;
+       } ctm;
+
+       /*struct css_style style;*/
+
+       /* paint attributes */
+       svgtiny_colour fill;
+       svgtiny_colour stroke;
+       int stroke_width;
+};
+
+
+static bool svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state);
+static bool svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state);
+static bool svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state);
+static bool svgtiny_parse_circle(xmlNode *circle,
+               struct svgtiny_parse_state state);
+static bool svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state);
+static bool svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state,
+               bool polygon);
+static bool svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state);
+static void svgtiny_parse_position_attributes(const xmlNode *node,
+               const struct svgtiny_parse_state state,
+               float *x, float *y, float *width, float *height);
+static float svgtiny_parse_length(const char *s, int viewport_size,
+               const struct svgtiny_parse_state state);
+static void svgtiny_parse_paint_attributes(const xmlNode *node,
+               struct svgtiny_parse_state *state);
+static void svgtiny_parse_color(const char *s, svgtiny_colour *c,
+               struct svgtiny_parse_state *state);
+static void svgtiny_parse_font_attributes(const xmlNode *node,
+               struct svgtiny_parse_state *state);
+static void svgtiny_parse_transform_attributes(xmlNode *node,
+               struct svgtiny_parse_state *state);
+static struct svgtiny_shape *svgtiny_add_shape(
+               struct svgtiny_parse_state *state);
+static void svgtiny_transform_path(float *p, unsigned int n,
+               struct svgtiny_parse_state *state);
+
+
+struct svgtiny_diagram *svgtiny_create(void)
+{
+       struct svgtiny_diagram *diagram;
+
+       diagram = malloc(sizeof *diagram);
+       if (!diagram)
+               return 0;
+
+       diagram->doc = 0;
+       diagram->svg = 0;
+       diagram->shape = 0;
+       diagram->shape_count = 0;
+
+       return diagram;
+}
+
+
+svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
+               const char *buffer, size_t size, const char *url,
+               int viewport_width, int viewport_height)
+{
+       xmlDoc *document;
+       xmlNode *svg;
+       struct svgtiny_parse_state state;
+
+       assert(diagram);
+       assert(buffer);
+       assert(url);
+
+       /* parse XML to tree */
+       document = xmlReadMemory(buffer, size, url, 0,
+                       XML_PARSE_NONET | XML_PARSE_COMPACT |
+                       XML_PARSE_DTDVALID /* needed for xmlGetID to work */);
+       if (!document)
+               return svgtiny_LIBXML_ERROR;
+       diagram->doc = document;
+
+       /*xmlDebugDumpDocument(stderr, document);*/
+
+       /* find root <svg> element */
+       svg = xmlDocGetRootElement(document);
+       if (!svg)
+               return svgtiny_NOT_SVG;
+       if (strcmp((const char *) svg->name, "svg") != 0)
+               return svgtiny_NOT_SVG;
+       diagram->svg = svg;
+
+       /* get graphic dimensions */
+       float x, y, width, height;
+       state.diagram = diagram;
+       state.document = diagram->doc;
+       state.viewport_width = viewport_width;
+       state.viewport_height = viewport_height;
+       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
+       diagram->width = width;
+       diagram->height = height;
+
+       state.viewport_width = width;
+       state.viewport_height = height;
+       state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
+       state.ctm.b = 0;
+       state.ctm.c = 0;
+       state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
+       state.ctm.e = 0; /*x;*/
+       state.ctm.f = 0; /*y;*/
+       /*state.style = css_base_style;
+       state.style.font_size.value.length.value = option_font_size * 0.1;*/
+       state.fill = 0x000000;
+       state.stroke = svgtiny_TRANSPARENT;
+       state.stroke_width = 1;
+
+       return svgtiny_parse_svg(svg, state);
+}
+
+
+/**
+ * Parse a <svg> or <g> element node.
+ */
+
+bool svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state)
+{
+       float x, y, width, height;
+
+       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
+       svgtiny_parse_paint_attributes(svg, &state);
+       svgtiny_parse_font_attributes(svg, &state);
+
+       /* parse viewBox */
+       xmlAttr *view_box = xmlHasProp(svg, (const xmlChar *) "viewBox");
+       if (view_box) {
+               const char *s = (const char *) view_box->children->content;
+               float min_x, min_y, vwidth, vheight;
+               if (sscanf(s, "%f,%f,%f,%f",
+                               &min_x, &min_y, &vwidth, &vheight) == 4 ||
+                               sscanf(s, "%f %f %f %f",
+                               &min_x, &min_y, &vwidth, &vheight) == 4) {
+                       state.ctm.a = (float) state.viewport_width / vwidth;
+                       state.ctm.d = (float) state.viewport_height / vheight;
+                       state.ctm.e += -min_x * state.ctm.a;
+                       state.ctm.f += -min_y * state.ctm.d;
+               }
+       }
+
+       svgtiny_parse_transform_attributes(svg, &state);
+
+       for (xmlNode *child = svg->children; child; child = child->next) {
+               bool ok = true;
+
+               if (child->type == XML_ELEMENT_NODE) {
+                       const char *name = (const char *) child->name;
+                       if (strcmp(name, "svg") == 0)
+                               ok = svgtiny_parse_svg(child, state);
+                       else if (strcmp(name, "g") == 0)
+                               ok = svgtiny_parse_svg(child, state);
+                       else if (strcmp(name, "a") == 0)
+                               ok = svgtiny_parse_svg(child, state);
+                       else if (strcmp(name, "path") == 0)
+                               ok = svgtiny_parse_path(child, state);
+                       else if (strcmp(name, "rect") == 0)
+                               ok = svgtiny_parse_rect(child, state);
+                       else if (strcmp(name, "circle") == 0)
+                               ok = svgtiny_parse_circle(child, state);
+                       else if (strcmp(name, "line") == 0)
+                               ok = svgtiny_parse_line(child, state);
+                       else if (strcmp(name, "polyline") == 0)
+                               ok = svgtiny_parse_poly(child, state, false);
+                       else if (strcmp(name, "polygon") == 0)
+                               ok = svgtiny_parse_poly(child, state, true);
+                       else if (strcmp(name, "text") == 0)
+                               ok = svgtiny_parse_text(child, state);
+               }
+
+               if (!ok)
+                       return false;
+       }
+
+       return true;
+}
+
+
+/**
+ * Parse a <path> element node.
+ *
+ * http://www.w3.org/TR/SVG11/paths#PathElement
+ */
+
+bool svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state)
+{
+       char *s, *path_d;
+
+       svgtiny_parse_paint_attributes(path, &state);
+       svgtiny_parse_transform_attributes(path, &state);
+
+       /* read d attribute */
+       s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d");
+       if (!s) {
+               /*LOG(("path missing d attribute"));*/
+               return false;
+       }
+
+       /* allocate space for path: it will never have more elements than d */
+       float *p = malloc(sizeof p[0] * strlen(s));
+       if (!p) {
+               /*LOG(("out of memory"));*/
+               return false;
+       }
+
+       /* parse d and build path */
+       for (unsigned int i = 0; s[i]; i++)
+               if (s[i] == ',')
+                       s[i] = ' ';
+       unsigned int i = 0;
+       float last_x = 0, last_y = 0;
+       float last_cubic_x = 0, last_cubic_y = 0;
+       float last_quad_x = 0, last_quad_y = 0;
+       while (*s) {
+               char command[2];
+               int plot_command;
+               float x, y, x1, y1, x2, y2;
+               int n;
+
+               /* moveto (M, m), lineto (L, l) (2 arguments) */
+               if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) {
+                       /*LOG(("moveto or lineto"));*/
+                       if (*command == 'M' || *command == 'm')
+                               plot_command = svgtiny_PATH_MOVE;
+                       else
+                               plot_command = svgtiny_PATH_LINE;
+                       do {
+                               p[i++] = plot_command;
+                               if ('a' <= *command) {
+                                       x += last_x;
+                                       y += last_y;
+                               }
+                               p[i++] = last_cubic_x = last_quad_x = last_x
+                                               = x;
+                               p[i++] = last_cubic_y = last_quad_y = last_y
+                                               = y;
+                               s += n;
+                               plot_command = svgtiny_PATH_LINE;
+                       } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
+
+               /* closepath (Z, z) (no arguments) */
+               } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
+                       /*LOG(("closepath"));*/
+                       p[i++] = svgtiny_PATH_CLOSE;
+                       s += n;
+
+               /* horizontal lineto (H, h) (1 argument) */
+               } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
+                       /*LOG(("horizontal lineto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_LINE;
+                               if (*command == 'h')
+                                       x += last_x;
+                               p[i++] = last_cubic_x = last_quad_x = last_x
+                                               = x;
+                               p[i++] = last_cubic_y = last_quad_y = last_y;
+                               s += n;
+                       } while (sscanf(s, "%f %n", &x, &n) == 1);
+
+               /* vertical lineto (V, v) (1 argument) */
+               } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
+                       /*LOG(("vertical lineto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_LINE;
+                               if (*command == 'v')
+                                       y += last_y;
+                               p[i++] = last_cubic_x = last_quad_x = last_x;
+                               p[i++] = last_cubic_y = last_quad_y = last_y
+                                               = y;
+                               s += n;
+                       } while (sscanf(s, "%f %n", &x, &n) == 1);
+
+               /* curveto (C, c) (6 arguments) */
+               } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command,
+                               &x1, &y1, &x2, &y2, &x, &y, &n) == 7) {
+                       /*LOG(("curveto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_BEZIER;
+                               if (*command == 'c') {
+                                       x1 += last_x;
+                                       y1 += last_y;
+                                       x2 += last_x;
+                                       y2 += last_y;
+                                       x += last_x;
+                                       y += last_y;
+                               }
+                               p[i++] = x1;
+                               p[i++] = y1;
+                               p[i++] = last_cubic_x = x2;
+                               p[i++] = last_cubic_y = y2;
+                               p[i++] = last_quad_x = last_x = x;
+                               p[i++] = last_quad_y = last_y = y;
+                               s += n;
+                       } while (sscanf(s, "%f %f %f %f %f %f %n",
+                                       &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
+
+               /* shorthand/smooth curveto (S, s) (4 arguments) */
+               } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
+                               &x2, &y2, &x, &y, &n) == 5) {
+                       /*LOG(("shorthand/smooth curveto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_BEZIER;
+                               x1 = last_x + (last_x - last_cubic_x);
+                               y1 = last_y + (last_y - last_cubic_y);
+                               if (*command == 's') {
+                                       x2 += last_x;
+                                       y2 += last_y;
+                                       x += last_x;
+                                       y += last_y;
+                               }
+                               p[i++] = x1;
+                               p[i++] = y1;
+                               p[i++] = last_cubic_x = x2;
+                               p[i++] = last_cubic_y = y2;
+                               p[i++] = last_quad_x = last_x = x;
+                               p[i++] = last_quad_y = last_y = y;
+                               s += n;
+                       } while (sscanf(s, "%f %f %f %f %n",
+                                       &x2, &y2, &x, &y, &n) == 4);
+
+               /* quadratic Bezier curveto (Q, q) (4 arguments) */
+               } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
+                               &x1, &y1, &x, &y, &n) == 5) {
+                       /*LOG(("quadratic Bezier curveto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_BEZIER;
+                               last_quad_x = x1;
+                               last_quad_y = y1;
+                               if (*command == 'q') {
+                                       x1 += last_x;
+                                       y1 += last_y;
+                                       x += last_x;
+                                       y += last_y;
+                               }
+                               p[i++] = 1./3 * last_x + 2./3 * x1;
+                               p[i++] = 1./3 * last_y + 2./3 * y1;
+                               p[i++] = 2./3 * x1 + 1./3 * x;
+                               p[i++] = 2./3 * y1 + 1./3 * y;
+                               p[i++] = last_cubic_x = last_x = x;
+                               p[i++] = last_cubic_y = last_y = y;
+                               s += n;
+                       } while (sscanf(s, "%f %f %f %f %n",
+                                       &x1, &y1, &x, &y, &n) == 4);
+
+               /* shorthand/smooth quadratic Bezier curveto (T, t)
+                  (2 arguments) */
+               } else if (sscanf(s, " %1[Tt] %f %f %n", command,
+                               &x, &y, &n) == 3) {
+                       /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
+                       do {
+                               p[i++] = svgtiny_PATH_BEZIER;
+                               x1 = last_x + (last_x - last_quad_x);
+                               y1 = last_y + (last_y - last_quad_y);
+                               last_quad_x = x1;
+                               last_quad_y = y1;
+                               if (*command == 't') {
+                                       x1 += last_x;
+                                       y1 += last_y;
+                                       x += last_x;
+                                       y += last_y;
+                               }
+                               p[i++] = 1./3 * last_x + 2./3 * x1;
+                               p[i++] = 1./3 * last_y + 2./3 * y1;
+                               p[i++] = 2./3 * x1 + 1./3 * x;
+                               p[i++] = 2./3 * y1 + 1./3 * y;
+                               p[i++] = last_cubic_x = last_x = x;
+                               p[i++] = last_cubic_y = last_y = y;
+                               s += n;
+                       } while (sscanf(s, "%f %f %n",
+                                       &x, &y, &n) == 2);
+
+               } else {
+                       /*LOG(("parse failed at \"%s\"", s));*/
+                       break;
+               }
+       }
+
+       xmlFree(path_d);
+
+       svgtiny_transform_path(p, i, &state);
+
+       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+       if (!shape) {
+               free(p);
+               return false;
+       }
+       shape->path = p;
+       shape->path_length = i;
+       state.diagram->shape_count++;
+
+       return true;
+}
+
+
+/**
+ * Parse a <rect> element node.
+ *
+ * http://www.w3.org/TR/SVG11/shapes#RectElement
+ */
+
+bool svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state)
+{
+       float x, y, width, height;
+
+       svgtiny_parse_position_attributes(rect, state,
+                       &x, &y, &width, &height);
+       svgtiny_parse_paint_attributes(rect, &state);
+       svgtiny_parse_transform_attributes(rect, &state);
+
+       float *p = malloc(13 * sizeof p[0]);
+       if (!p)
+               return false;
+
+       p[0] = svgtiny_PATH_MOVE;
+       p[1] = x;
+       p[2] = y;
+       p[3] = svgtiny_PATH_LINE;
+       p[4] = x + width;
+       p[5] = y;
+       p[6] = svgtiny_PATH_LINE;
+       p[7] = x + width;
+       p[8] = y + height;
+       p[9] = svgtiny_PATH_LINE;
+       p[10] = x;
+       p[11] = y + height;
+       p[12] = svgtiny_PATH_CLOSE;
+
+       svgtiny_transform_path(p, 13, &state);
+
+       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+       if (!shape) {
+               free(p);
+               return false;
+       }
+       shape->path = p;
+       shape->path_length = 13;
+       state.diagram->shape_count++;
+
+       return true;
+}
+
+
+/**
+ * Parse a <circle> element node.
+ */
+
+bool svgtiny_parse_circle(xmlNode *circle, struct svgtiny_parse_state state)
+{
+       float x = 0, y = 0, r = 0;
+       const float kappa = 0.5522847498;
+
+       for (xmlAttr *attr = circle->properties; attr; attr = attr->next) {
+               const char *name = (const char *) attr->name;
+               const char *content = (const char *) attr->children->content;
+               if (strcmp(name, "cx") == 0)
+                       x = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "cy") == 0)
+                       y = svgtiny_parse_length(content,
+                                       state.viewport_height, state);
+               else if (strcmp(name, "r") == 0)
+                       r = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+        }
+       svgtiny_parse_paint_attributes(circle, &state);
+       svgtiny_parse_transform_attributes(circle, &state);
+
+       float *p = malloc(32 * sizeof p[0]);
+       if (!p)
+               return false;
+
+       p[0] = svgtiny_PATH_MOVE;
+       p[1] = x - r;
+       p[2] = y;
+       p[3] = svgtiny_PATH_BEZIER;
+       p[4] = x - r;
+       p[5] = y + r * kappa;
+       p[6] = x - r * kappa;
+       p[7] = y + r;
+       p[8] = x;
+       p[9] = y + r;
+       p[10] = svgtiny_PATH_BEZIER;
+       p[11] = x + r * kappa;
+       p[12] = y + r;
+       p[13] = x + r;
+       p[14] = y + r * kappa;
+       p[15] = x + r;
+       p[16] = y;
+       p[17] = svgtiny_PATH_BEZIER;
+       p[18] = x + r;
+       p[19] = y - r * kappa;
+       p[20] = x + r * kappa;
+       p[21] = y - r;
+       p[22] = x;
+       p[23] = y - r;
+       p[24] = svgtiny_PATH_BEZIER;
+       p[25] = x - r * kappa;
+       p[26] = y - r;
+       p[27] = x - r;
+       p[28] = y - r * kappa;
+       p[29] = x - r;
+       p[30] = y;
+       p[31] = svgtiny_PATH_CLOSE;
+       
+       svgtiny_transform_path(p, 32, &state);
+
+       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+       if (!shape) {
+               free(p);
+               return false;
+       }
+       shape->path = p;
+       shape->path_length = 32;
+       state.diagram->shape_count++;
+
+       return true;
+}
+
+
+/**
+ * Parse a <line> element node.
+ */
+
+bool svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state)
+{
+       float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+
+       for (xmlAttr *attr = line->properties; attr; attr = attr->next) {
+               const char *name = (const char *) attr->name;
+               const char *content = (const char *) attr->children->content;
+               if (strcmp(name, "x1") == 0)
+                       x1 = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "y1") == 0)
+                       y1 = svgtiny_parse_length(content,
+                                       state.viewport_height, state);
+               else if (strcmp(name, "x2") == 0)
+                       x2 = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "y2") == 0)
+                       y2 = svgtiny_parse_length(content,
+                                       state.viewport_height, state);
+        }
+       svgtiny_parse_paint_attributes(line, &state);
+       svgtiny_parse_transform_attributes(line, &state);
+
+       float *p = malloc(7 * sizeof p[0]);
+       if (!p)
+               return false;
+
+       p[0] = svgtiny_PATH_MOVE;
+       p[1] = x1;
+       p[2] = y1;
+       p[3] = svgtiny_PATH_LINE;
+       p[4] = x2;
+       p[5] = y2;
+       p[6] = svgtiny_PATH_CLOSE;
+
+       svgtiny_transform_path(p, 7, &state);
+
+       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+       if (!shape) {
+               free(p);
+               return false;
+       }
+       shape->path = p;
+       shape->path_length = 7;
+       state.diagram->shape_count++;
+
+       return true;
+}
+
+
+/**
+ * Parse a <polyline> or <polygon> element node.
+ *
+ * http://www.w3.org/TR/SVG11/shapes#PolylineElement
+ * http://www.w3.org/TR/SVG11/shapes#PolygonElement
+ */
+
+bool svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state,
+               bool polygon)
+{
+       char *s, *points;
+
+       svgtiny_parse_paint_attributes(poly, &state);
+       svgtiny_parse_transform_attributes(poly, &state);
+
+       /* read d attribute */
+       s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points");
+       if (!s) {
+               /*LOG(("poly missing d attribute"));*/
+               return false;
+       }
+
+       /* allocate space for path: it will never have more elements than s */
+       float *p = malloc(sizeof p[0] * strlen(s));
+       if (!p) {
+               /*LOG(("out of memory"));*/
+               return false;
+       }
+
+       /* parse s and build path */
+       for (unsigned int i = 0; s[i]; i++)
+               if (s[i] == ',')
+                       s[i] = ' ';
+       unsigned int i = 0;
+       while (*s) {
+               float x, y;
+               int n;
+
+               if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) {
+                       if (i == 0)
+                               p[i++] = svgtiny_PATH_MOVE;
+                       else
+                               p[i++] = svgtiny_PATH_LINE;
+                       p[i++] = x;
+                       p[i++] = y;
+                       s += n;
+                } else {
+                       break;
+                }
+        }
+        if (polygon)
+               p[i++] = svgtiny_PATH_CLOSE;
+
+       xmlFree(points);
+
+       svgtiny_transform_path(p, i, &state);
+
+       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+       if (!shape) {
+               free(p);
+               return false;
+       }
+       shape->path = p;
+       shape->path_length = i;
+       state.diagram->shape_count++;
+
+       return true;
+}
+
+
+/**
+ * Parse a <text> or <tspan> element node.
+ */
+
+bool svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state)
+{
+       float x, y, width, height;
+
+       svgtiny_parse_position_attributes(text, state,
+                       &x, &y, &width, &height);
+       svgtiny_parse_font_attributes(text, &state);
+       svgtiny_parse_transform_attributes(text, &state);
+
+       float px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
+       float py = state.ctm.b * x + state.ctm.d * y + state.ctm.f;
+/*     state.ctm.e = px - state.origin_x; */
+/*     state.ctm.f = py - state.origin_y; */
+
+       /*struct css_style style = state.style;
+       style.font_size.value.length.value *= state.ctm.a;*/
+
+       for (xmlNode *child = text->children; child; child = child->next) {
+               bool ok = true;
+
+               if (child->type == XML_TEXT_NODE) {
+                       struct svgtiny_shape *shape = svgtiny_add_shape(&state);
+                       if (!shape)
+                               return false;
+                       shape->text = strdup((const char *) child->content);
+                       shape->text_x = px;
+                       shape->text_y = py;
+                       state.diagram->shape_count++;
+
+               } else if (child->type == XML_ELEMENT_NODE &&
+                               strcmp((const char *) child->name,
+                                       "tspan") == 0) {
+                       ok = svgtiny_parse_text(child, state);
+               }
+
+               if (!ok)
+                       return false;
+       }
+
+       return true;
+}
+
+
+/**
+ * Parse x, y, width, and height attributes, if present.
+ */
+
+void svgtiny_parse_position_attributes(const xmlNode *node,
+               const struct svgtiny_parse_state state,
+               float *x, float *y, float *width, float *height)
+{
+       *x = 0;
+       *y = 0;
+       *width = state.viewport_width;
+       *height = state.viewport_height;
+
+       for (xmlAttr *attr = node->properties; attr; attr = attr->next) {
+               const char *name = (const char *) attr->name;
+               const char *content = (const char *) attr->children->content;
+               if (strcmp(name, "x") == 0)
+                       *x = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "y") == 0)
+                       *y = svgtiny_parse_length(content,
+                                       state.viewport_height, state);
+               else if (strcmp(name, "width") == 0)
+                       *width = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "height") == 0)
+                       *height = svgtiny_parse_length(content,
+                                       state.viewport_height, state);
+       }
+}
+
+
+/**
+ * Parse a length as a number of pixels.
+ */
+
+float svgtiny_parse_length(const char *s, int viewport_size,
+               const struct svgtiny_parse_state state)
+{
+       int num_length = strspn(s, "0123456789+-.");
+       const char *unit = s + num_length;
+       float n = atof((const char *) s);
+       float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
+
+       if (unit[0] == 0) {
+               return n;
+       } else if (unit[0] == '%') {
+               return n / 100.0 * viewport_size;
+       } else if (unit[0] == 'e' && unit[1] == 'm') {
+               return n * font_size;
+       } else if (unit[0] == 'e' && unit[1] == 'x') {
+               return n / 2.0 * font_size;
+       } else if (unit[0] == 'p' && unit[1] == 'x') {
+               return n;
+       } else if (unit[0] == 'p' && unit[1] == 't') {
+               return n * 1.25;
+       } else if (unit[0] == 'p' && unit[1] == 'c') {
+               return n * 15.0;
+       } else if (unit[0] == 'm' && unit[1] == 'm') {
+               return n * 3.543307;
+       } else if (unit[0] == 'c' && unit[1] == 'm') {
+               return n * 35.43307;
+       } else if (unit[0] == 'i' && unit[1] == 'n') {
+               return n * 90;
+       }
+
+       return 0;
+}
+
+
+/**
+ * Parse paint attributes, if present.
+ */
+
+void svgtiny_parse_paint_attributes(const xmlNode *node,
+               struct svgtiny_parse_state *state)
+{
+       for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
+               const char *name = (const char *) attr->name;
+               const char *content = (const char *) attr->children->content;
+               if (strcmp(name, "fill") == 0)
+                       svgtiny_parse_color(content, &state->fill, state);
+               else if (strcmp(name, "stroke") == 0)
+                       svgtiny_parse_color(content, &state->stroke, state);
+               else if (strcmp(name, "stroke-width") == 0)
+                       state->stroke_width = svgtiny_parse_length(content,
+                                       state->viewport_width, *state);
+               else if (strcmp(name, "style") == 0) {
+                       const char *style = (const char *)
+                                       attr->children->content;
+                       const char *s;
+                       char *value;
+                       if ((s = strstr(style, "fill:"))) {
+                               s += 5;
+                               while (*s == ' ')
+                                       s++;
+                               value = strndup(s, strcspn(s, "; "));
+                               svgtiny_parse_color(value, &state->fill, state);
+                               free(value);
+                       }
+                       if ((s = strstr(style, "stroke:"))) {
+                               s += 7;
+                               while (*s == ' ')
+                                       s++;
+                               value = strndup(s, strcspn(s, "; "));
+                               svgtiny_parse_color(value, &state->stroke, state);
+                               free(value);
+                       }
+                       if ((s = strstr(style, "stroke-width:"))) {
+                               s += 13;
+                               while (*s == ' ')
+                                       s++;
+                               state->stroke_width = svgtiny_parse_length(s,
+                                               state->viewport_width, *state);
+                       }
+               }
+       }
+}
+
+
+/**
+ * Parse a colour.
+ */
+
+void svgtiny_parse_color(const char *s, svgtiny_colour *c,
+               struct svgtiny_parse_state *state)
+{
+       unsigned int r, g, b;
+       float rf, gf, bf;
+       size_t len = strlen(s);
+       char *id = 0, *rparen;
+       xmlAttr *id_attr;
+
+       if (len == 4 && s[0] == '#') {
+               if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
+                       *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
+
+       } else if (len == 7 && s[0] == '#') {
+               if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
+                       *c = svgtiny_RGB(r, g, b);
+
+       } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
+                       s[3] == '(' && s[len - 1] == ')') {
+               if (sscanf(s + 4, "%i,%i,%i", &r, &g, &b) == 3)
+                       *c = svgtiny_RGB(r, g, b);
+               else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
+                       b = bf * 255 / 100;
+                       g = gf * 255 / 100;
+                       r = rf * 255 / 100;
+                       *c = svgtiny_RGB(r, g, b);
+               }
+
+       } else if (len == 4 && strcmp(s, "none") == 0) {
+               *c = svgtiny_TRANSPARENT;
+
+       } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
+                       s[3] == '(') {
+               if (s[4] == '#') {
+                       id = strdup(s + 5);
+                       if (!id)
+                               return;
+                       rparen = strchr(id, ')');
+                       if (rparen)
+                               *rparen = 0;
+                       id_attr = xmlGetID(state->document,
+                                       (const xmlChar *) id);
+                       if (!id_attr) {
+                               fprintf(stderr, "id \"%s\" not found\n", id);
+                               free(id);
+                               return;
+                       }
+                       fprintf(stderr, "id \"%s\" at %p\n", id, id_attr);
+                       free(id);
+               }
+
+       } else {
+               const struct svgtiny_named_color *named_color;
+               named_color = svgtiny_color_lookup(s, strlen(s));
+               if (named_color)
+                       *c = named_color->color;
+       }
+}
+
+
+/**
+ * Parse font attributes, if present.
+ */
+
+void svgtiny_parse_font_attributes(const xmlNode *node,
+               struct svgtiny_parse_state *state)
+{
+       for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
+               if (strcmp((const char *) attr->name, "font-size") == 0) {
+                       /*if (css_parse_length(
+                                       (const char *) attr->children->content,
+                                       &state->style.font_size.value.length,
+                                       true, true)) {
+                               state->style.font_size.size =
+                                               CSS_FONT_SIZE_LENGTH;
+                       }*/
+               }
+        }
+}
+
+
+/**
+ * Parse transform attributes, if present.
+ *
+ * http://www.w3.org/TR/SVG11/coords#TransformAttribute
+ */
+
+void svgtiny_parse_transform_attributes(xmlNode *node,
+               struct svgtiny_parse_state *state)
+{
+       char *transform, *s;
+       float a, b, c, d, e, f;
+       float ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
+       float angle, x, y;
+       int n;
+
+       /* parse transform */
+       s = transform = (char *) xmlGetProp(node,
+                       (const xmlChar *) "transform");
+       if (transform) {
+               for (unsigned int i = 0; transform[i]; i++)
+                       if (transform[i] == ',')
+                               transform[i] = ' ';
+
+               while (*s) {
+                       a = d = 1;
+                       b = c = 0;
+                       e = f = 0;
+                       if (sscanf(s, "matrix (%f %f %f %f %f %f) %n",
+                                       &a, &b, &c, &d, &e, &f, &n) == 6)
+                               ;
+                       else if (sscanf(s, "translate (%f %f) %n",
+                                       &e, &f, &n) == 2)
+                               ;
+                       else if (sscanf(s, "translate (%f) %n",
+                                       &e, &n) == 1)
+                               ;
+                       else if (sscanf(s, "scale (%f %f) %n",
+                                       &a, &d, &n) == 2)
+                               ;
+                       else if (sscanf(s, "scale (%f) %n",
+                                       &a, &n) == 1)
+                               d = a;
+                       else if (sscanf(s, "rotate (%f %f %f) %n",
+                                       &angle, &x, &y, &n) == 3) {
+                               angle = angle / 180 * M_PI;
+                               a = cos(angle);
+                               b = sin(angle);
+                               c = -sin(angle);
+                               d = cos(angle);
+                               e = -x * cos(angle) + y * sin(angle) + x;
+                               f = -x * sin(angle) - y * cos(angle) + y;
+                       } else if (sscanf(s, "rotate (%f) %n",
+                                       &angle, &n) == 1) {
+                               angle = angle / 180 * M_PI;
+                               a = cos(angle);
+                               b = sin(angle);
+                               c = -sin(angle);
+                               d = cos(angle);
+                       } else if (sscanf(s, "skewX (%f) %n",
+                                       &angle, &n) == 1) {
+                               angle = angle / 180 * M_PI;
+                               c = tan(angle);
+                       } else if (sscanf(s, "skewY (%f) %n",
+                                       &angle, &n) == 1) {
+                               angle = angle / 180 * M_PI;
+                               b = tan(angle);
+                       } else
+                               break;
+                       ctm_a = state->ctm.a * a + state->ctm.c * b;
+                       ctm_b = state->ctm.b * a + state->ctm.d * b;
+                       ctm_c = state->ctm.a * c + state->ctm.c * d;
+                       ctm_d = state->ctm.b * c + state->ctm.d * d;
+                       ctm_e = state->ctm.a * e + state->ctm.c * f +
+                                       state->ctm.e;
+                       ctm_f = state->ctm.b * e + state->ctm.d * f +
+                                       state->ctm.f;
+                       state->ctm.a = ctm_a;
+                       state->ctm.b = ctm_b;
+                       state->ctm.c = ctm_c;
+                       state->ctm.d = ctm_d;
+                       state->ctm.e = ctm_e;
+                       state->ctm.f = ctm_f;
+                       s += n;
+               }
+
+               xmlFree(transform);
+       }
+}
+
+
+/**
+ * Add a svgtiny_shape to the svgtiny_diagram.
+ */
+
+struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
+{
+       struct svgtiny_shape *shape = realloc(state->diagram->shape,
+                       (state->diagram->shape_count + 1) *
+                       sizeof (state->diagram->shape[0]));
+       if (!shape)
+               return 0;
+       state->diagram->shape = shape;
+
+       shape += state->diagram->shape_count;
+       shape->path = 0;
+       shape->path_length = 0;
+       shape->text = 0;
+       shape->fill = state->fill;
+       shape->stroke = state->stroke;
+       shape->stroke_width = state->stroke_width;
+
+       return shape;
+}
+
+
+void svgtiny_transform_path(float *p, unsigned int n,
+               struct svgtiny_parse_state *state)
+{
+       for (unsigned int j = 0; j != n; ) {
+               unsigned int points = 0;
+               switch ((int) p[j]) {
+               case svgtiny_PATH_MOVE:
+               case svgtiny_PATH_LINE:
+                       points = 1;
+                       break;
+               case svgtiny_PATH_CLOSE:
+                       points = 0;
+                       break;
+               case svgtiny_PATH_BEZIER:
+                       points = 3;
+                       break;
+               default:
+                       assert(0);
+               }
+               j++;
+               for (unsigned int k = 0; k != points; k++) {
+                       float x0 = p[j], y0 = p[j + 1];
+                       float x = state->ctm.a * x0 + state->ctm.c * y0 +
+                               state->ctm.e;
+                       float y = state->ctm.b * x0 + state->ctm.d * y0 +
+                               state->ctm.f;
+                       p[j] = x;
+                       p[j + 1] = y;
+                       j += 2;
+               }
+       }
+}
+
diff --git a/svgtiny.h b/svgtiny.h
new file mode 100644 (file)
index 0000000..a5017d1
--- /dev/null
+++ b/svgtiny.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2008 James Bursa <james@semichrome.net>
+ */
+
+#ifndef SVGTINY_H
+#define SVGTINY_H
+
+#include <libxml/parser.h>
+
+typedef int svgtiny_colour;
+#define svgtiny_TRANSPARENT 0x1000000
+#define svgtiny_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b))
+
+struct svgtiny_shape {
+       float *path;
+       unsigned int path_length;
+       char *text;
+       float text_x, text_y;
+       svgtiny_colour fill;
+       svgtiny_colour stroke;
+       int stroke_width;
+};
+
+struct svgtiny_diagram {
+       xmlDoc *doc;
+       xmlNode *svg;
+
+       int width, height;
+
+       struct svgtiny_shape *shape;
+       unsigned int shape_count;
+};
+
+typedef enum {
+       svgtiny_OK,
+       svgtiny_OUT_OF_MEMORY,
+       svgtiny_LIBXML_ERROR,
+       svgtiny_NOT_SVG,
+} svgtiny_code;
+
+enum {
+       svgtiny_PATH_MOVE,
+       svgtiny_PATH_CLOSE,
+       svgtiny_PATH_LINE,
+       svgtiny_PATH_BEZIER,
+};
+
+struct svgtiny_named_color {
+       const char *name;
+       svgtiny_colour color;
+};
+
+
+struct svgtiny_diagram *svgtiny_create(void);
+svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
+               const char *buffer, size_t size, const char *url,
+               int width, int height);
+void svgtiny_free(struct svgtiny_diagram *svg);
+
+const struct svgtiny_named_color *
+svgtiny_color_lookup (register const char *str, register unsigned int len);
+
+#endif
diff --git a/svgtiny_display b/svgtiny_display
new file mode 100755 (executable)
index 0000000..4bc1396
--- /dev/null
@@ -0,0 +1,3 @@
+set -e
+make
+./svgtiny_test $1 | convert mvg:- png:- | display -
diff --git a/svgtiny_test.c b/svgtiny_test.c
new file mode 100644 (file)
index 0000000..9bb7e2e
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2008 James Bursa <james@semichrome.net>
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "svgtiny.h"
+
+
+int main(int argc, char *argv[])
+{
+       FILE *fd;
+       struct stat sb;
+       char *buffer;
+       size_t size;
+       size_t n;
+       struct svgtiny_diagram *diagram;
+       svgtiny_code code;
+
+       if (argc != 2) {
+               fprintf(stderr, "Usage: %s FILE\n", argv[0]);
+               return 1;
+       }
+
+       /* load file into memory buffer */
+       fd = fopen(argv[1], "rb");
+       if (!fd) {
+               perror(argv[1]);
+               return 1;
+       }
+
+       if (stat(argv[1], &sb)) {
+               perror(argv[1]);
+               return 1;
+       }
+       size = sb.st_size;
+
+       fprintf(stderr, "size: %lld bytes\n", (long long) size);
+
+       buffer = malloc(size);
+       if (!buffer) {
+               fprintf(stderr, "Unable to allocate %lld bytes\n",
+                               (long long) size);
+               return 1;
+       }
+
+       n = fread(buffer, 1, size, fd);
+       if (n != size) {
+               perror(argv[1]);
+               return 1;
+       }
+
+       fclose(fd);
+
+       /* create svgtiny object */
+       diagram = svgtiny_create();
+       if (!diagram) {
+               fprintf(stderr, "svgtiny_create failed\n");
+               return 1;
+       }
+
+       /* parse */
+       code = svgtiny_parse(diagram, buffer, size, argv[1], 1000, 1000);
+       if (code != svgtiny_OK)
+               fprintf(stderr, "svgtiny_parse failed: %i\n", code);
+
+       printf("viewbox 0 0 %i %i\n", diagram->width, diagram->height);
+
+       for (unsigned int i = 0; i != diagram->shape_count; i++) {
+               if (diagram->shape[i].fill == svgtiny_TRANSPARENT)
+                       printf("fill none ");
+               else
+                       printf("fill #%.6x ", diagram->shape[i].fill);
+               if (diagram->shape[i].stroke == svgtiny_TRANSPARENT)
+                       printf("stroke none ");
+               else
+                       printf("stroke #%.6x ", diagram->shape[i].stroke);
+               if (diagram->shape[i].path) {
+                       printf("path '");
+                       for (unsigned int j = 0;
+                                       j != diagram->shape[i].path_length; ) {
+                               switch ((int) diagram->shape[i].path[j]) {
+                               case svgtiny_PATH_MOVE:
+                                       printf("M %g %g ",
+                                               diagram->shape[i].path[j + 1],
+                                               diagram->shape[i].path[j + 2]);
+                                       j += 3;
+                                       break;
+                               case svgtiny_PATH_CLOSE:
+                                       printf("Z ");
+                                       j += 1;
+                                       break;
+                               case svgtiny_PATH_LINE:
+                                       printf("L %g %g ",
+                                               diagram->shape[i].path[j + 1],
+                                               diagram->shape[i].path[j + 2]);
+                                       j += 3;
+                                       break;
+                               case svgtiny_PATH_BEZIER:
+                                       printf("C %g %g %g %g %g %g ",
+                                               diagram->shape[i].path[j + 1],
+                                               diagram->shape[i].path[j + 2],
+                                               diagram->shape[i].path[j + 3],
+                                               diagram->shape[i].path[j + 4],
+                                               diagram->shape[i].path[j + 5],
+                                               diagram->shape[i].path[j + 6]);
+                                       j += 7;
+                                       break;
+                               default:
+                                       printf("error ");
+                                       j += 1;
+                               }
+                       }
+                       printf("' ");
+               } else if (diagram->shape[i].text) {
+                       printf("text %g %g '%s' ",
+                                       diagram->shape[i].text_x,
+                                       diagram->shape[i].text_y,
+                                       diagram->shape[i].text);
+               }
+               printf("\n");
+       }
+
+       return 0;
+}
+