]> gitweb.michael.orlitzky.com - libsvgtiny.git/blobdiff - src/svgtiny.c
Update to new NSBUILD infrastructure
[libsvgtiny.git] / src / svgtiny.c
index 56f0028ccecc315ec3211f7dc275907a5adc4374..8fa09a5bdd6e66ed2cdbdcb01d690af3f277e0f3 100644 (file)
@@ -2,10 +2,9 @@
  * 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>
+ * Copyright 2008-2009 James Bursa <james@semichrome.net>
  */
 
-#define _GNU_SOURCE  /* for strndup */
 #include <assert.h>
 #include <math.h>
 #include <setjmp.h>
 #include "svgtiny.h"
 #include "svgtiny_internal.h"
 
+#ifndef M_PI
+#define M_PI           3.14159265358979323846
+#endif
+
+#define KAPPA          0.5522847498
 
 static svgtiny_code svgtiny_parse_svg(xmlNode *svg,
                struct svgtiny_parse_state state);
@@ -27,6 +31,8 @@ static svgtiny_code svgtiny_parse_rect(xmlNode *rect,
                struct svgtiny_parse_state state);
 static svgtiny_code svgtiny_parse_circle(xmlNode *circle,
                struct svgtiny_parse_state state);
+static svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse,
+               struct svgtiny_parse_state state);
 static svgtiny_code svgtiny_parse_line(xmlNode *line,
                struct svgtiny_parse_state state);
 static svgtiny_code svgtiny_parse_poly(xmlNode *poly,
@@ -143,13 +149,15 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg,
                struct svgtiny_parse_state state)
 {
        float x, y, width, height;
+       xmlAttr *view_box;
+       xmlNode *child;
 
        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");
+       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;
@@ -166,7 +174,7 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg,
 
        svgtiny_parse_transform_attributes(svg, &state);
 
-       for (xmlNode *child = svg->children; child; child = child->next) {
+       for (child = svg->children; child; child = child->next) {
                svgtiny_code code = svgtiny_OK;
 
                if (child->type == XML_ELEMENT_NODE) {
@@ -183,6 +191,8 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg,
                                code = svgtiny_parse_rect(child, state);
                        else if (strcmp(name, "circle") == 0)
                                code = svgtiny_parse_circle(child, state);
+                       else if (strcmp(name, "ellipse") == 0)
+                               code = svgtiny_parse_ellipse(child, state);
                        else if (strcmp(name, "line") == 0)
                                code = svgtiny_parse_line(child, state);
                        else if (strcmp(name, "polyline") == 0)
@@ -212,6 +222,11 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                struct svgtiny_parse_state state)
 {
        char *s, *path_d;
+       float *p;
+       unsigned int i;
+       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;
 
        svgtiny_parse_paint_attributes(path, &state);
        svgtiny_parse_transform_attributes(path, &state);
@@ -225,22 +240,19 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
        }
 
        /* allocate space for path: it will never have more elements than d */
-       float *p = malloc(sizeof p[0] * strlen(s));
+       p = malloc(sizeof p[0] * strlen(s));
        if (!p)
                return svgtiny_OUT_OF_MEMORY;
 
        /* parse d and build path */
-       for (unsigned int i = 0; s[i]; i++)
+       for (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;
+       i = 0;
        while (*s) {
                char command[2];
                int plot_command;
-               float x, y, x1, y1, x2, y2;
+               float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
                int n;
 
                /* moveto (M, m), lineto (L, l) (2 arguments) */
@@ -395,14 +407,39 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                        } while (sscanf(s, "%f %f %n",
                                        &x, &y, &n) == 2);
 
+               /* elliptical arc (A, a) (7 arguments) */
+               } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
+                               &rx, &ry, &rotation, &large_arc, &sweep,
+                               &x, &y, &n) == 8) {
+                       do {
+                               p[i++] = svgtiny_PATH_LINE;
+                               if (*command == 'a') {
+                                       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;
+                       } while (sscanf(s, "%f %f %f %f %f %f %f %n",
+                               &rx, &ry, &rotation, &large_arc, &sweep,
+                               &x, &y, &n) == 7);
+
                } else {
-                       /*LOG(("parse failed at \"%s\"", s));*/
+                       fprintf(stderr, "parse failed at \"%s\"\n", s);
                        break;
                }
        }
 
        xmlFree(path_d);
 
+       if (i <= 4) {
+               /* no real segments in path */
+               free(p);
+               return svgtiny_OK;
+       }
+
        return svgtiny_add_path(p, i, &state);
 }
 
@@ -417,13 +454,14 @@ svgtiny_code svgtiny_parse_rect(xmlNode *rect,
                struct svgtiny_parse_state state)
 {
        float x, y, width, height;
+       float *p;
 
        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]);
+       p = malloc(13 * sizeof p[0]);
        if (!p)
                return svgtiny_OUT_OF_MEMORY;
 
@@ -452,10 +490,11 @@ svgtiny_code svgtiny_parse_rect(xmlNode *rect,
 svgtiny_code svgtiny_parse_circle(xmlNode *circle,
                struct svgtiny_parse_state state)
 {
-       float x = 0, y = 0, r = 0;
-       const float kappa = 0.5522847498;
+       float x = 0, y = 0, r = -1;
+       float *p;
+       xmlAttr *attr;
 
-       for (xmlAttr *attr = circle->properties; attr; attr = attr->next) {
+       for (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)
@@ -471,40 +510,128 @@ svgtiny_code svgtiny_parse_circle(xmlNode *circle,
        svgtiny_parse_paint_attributes(circle, &state);
        svgtiny_parse_transform_attributes(circle, &state);
 
-       float *p = malloc(32 * sizeof p[0]);
+       if (r < 0) {
+               state.diagram->error_line = circle->line;
+               state.diagram->error_message = "circle: r missing or negative";
+               return svgtiny_SVG_ERROR;
+       }
+       if (r == 0)
+               return svgtiny_OK;
+
+       p = malloc(32 * sizeof p[0]);
        if (!p)
                return svgtiny_OUT_OF_MEMORY;
 
        p[0] = svgtiny_PATH_MOVE;
-       p[1] = x - r;
+       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[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[11] = x - r * KAPPA;
        p[12] = y + r;
-       p[13] = x + r;
-       p[14] = y + r * kappa;
-       p[15] = x + 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[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[25] = x + r * KAPPA;
        p[26] = y - r;
-       p[27] = x - r;
-       p[28] = y - r * kappa;
-       p[29] = x - r;
+       p[27] = x + r;
+       p[28] = y - r * KAPPA;
+       p[29] = x + r;
+       p[30] = y;
+       p[31] = svgtiny_PATH_CLOSE;
+       
+       return svgtiny_add_path(p, 32, &state);
+}
+
+
+/**
+ * Parse an <ellipse> element node.
+ */
+
+svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse,
+               struct svgtiny_parse_state state)
+{
+       float x = 0, y = 0, rx = -1, ry = -1;
+       float *p;
+       xmlAttr *attr;
+
+       for (attr = ellipse->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, "rx") == 0)
+                       rx = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+               else if (strcmp(name, "ry") == 0)
+                       ry = svgtiny_parse_length(content,
+                                       state.viewport_width, state);
+        }
+       svgtiny_parse_paint_attributes(ellipse, &state);
+       svgtiny_parse_transform_attributes(ellipse, &state);
+
+       if (rx < 0 || ry < 0) {
+               state.diagram->error_line = ellipse->line;
+               state.diagram->error_message = "ellipse: rx or ry missing "
+                               "or negative";
+               return svgtiny_SVG_ERROR;
+       }
+       if (rx == 0 || ry == 0)
+               return svgtiny_OK;
+
+       p = malloc(32 * sizeof p[0]);
+       if (!p)
+               return svgtiny_OUT_OF_MEMORY;
+
+       p[0] = svgtiny_PATH_MOVE;
+       p[1] = x + rx;
+       p[2] = y;
+       p[3] = svgtiny_PATH_BEZIER;
+       p[4] = x + rx;
+       p[5] = y + ry * KAPPA;
+       p[6] = x + rx * KAPPA;
+       p[7] = y + ry;
+       p[8] = x;
+       p[9] = y + ry;
+       p[10] = svgtiny_PATH_BEZIER;
+       p[11] = x - rx * KAPPA;
+       p[12] = y + ry;
+       p[13] = x - rx;
+       p[14] = y + ry * KAPPA;
+       p[15] = x - rx;
+       p[16] = y;
+       p[17] = svgtiny_PATH_BEZIER;
+       p[18] = x - rx;
+       p[19] = y - ry * KAPPA;
+       p[20] = x - rx * KAPPA;
+       p[21] = y - ry;
+       p[22] = x;
+       p[23] = y - ry;
+       p[24] = svgtiny_PATH_BEZIER;
+       p[25] = x + rx * KAPPA;
+       p[26] = y - ry;
+       p[27] = x + rx;
+       p[28] = y - ry * KAPPA;
+       p[29] = x + rx;
        p[30] = y;
        p[31] = svgtiny_PATH_CLOSE;
        
@@ -520,8 +647,10 @@ svgtiny_code svgtiny_parse_line(xmlNode *line,
                struct svgtiny_parse_state state)
 {
        float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+       float *p;
+       xmlAttr *attr;
 
-       for (xmlAttr *attr = line->properties; attr; attr = attr->next) {
+       for (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)
@@ -540,7 +669,7 @@ svgtiny_code svgtiny_parse_line(xmlNode *line,
        svgtiny_parse_paint_attributes(line, &state);
        svgtiny_parse_transform_attributes(line, &state);
 
-       float *p = malloc(7 * sizeof p[0]);
+       p = malloc(7 * sizeof p[0]);
        if (!p)
                return svgtiny_OUT_OF_MEMORY;
 
@@ -567,6 +696,8 @@ svgtiny_code svgtiny_parse_poly(xmlNode *poly,
                struct svgtiny_parse_state state, bool polygon)
 {
        char *s, *points;
+       float *p;
+       unsigned int i;
 
        svgtiny_parse_paint_attributes(poly, &state);
        svgtiny_parse_transform_attributes(poly, &state);
@@ -581,17 +712,17 @@ svgtiny_code svgtiny_parse_poly(xmlNode *poly,
        }
 
        /* allocate space for path: it will never have more elements than s */
-       float *p = malloc(sizeof p[0] * strlen(s));
+       p = malloc(sizeof p[0] * strlen(s));
        if (!p) {
                xmlFree(points);
                return svgtiny_OUT_OF_MEMORY;
        }
 
        /* parse s and build path */
-       for (unsigned int i = 0; s[i]; i++)
+       for (i = 0; s[i]; i++)
                if (s[i] == ',')
                        s[i] = ' ';
-       unsigned int i = 0;
+       i = 0;
        while (*s) {
                float x, y;
                int n;
@@ -625,21 +756,23 @@ svgtiny_code svgtiny_parse_text(xmlNode *text,
                struct svgtiny_parse_state state)
 {
        float x, y, width, height;
+       float px, py;
+       xmlNode *child;
 
        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;
+       px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
+       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) {
+       for (child = text->children; child; child = child->next) {
                svgtiny_code code = svgtiny_OK;
 
                if (child->type == XML_TEXT_NODE) {
@@ -673,12 +806,14 @@ void svgtiny_parse_position_attributes(const xmlNode *node,
                const struct svgtiny_parse_state state,
                float *x, float *y, float *width, float *height)
 {
+       xmlAttr *attr;
+
        *x = 0;
        *y = 0;
        *width = state.viewport_width;
        *height = state.viewport_height;
 
-       for (xmlAttr *attr = node->properties; attr; attr = attr->next) {
+       for (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)
@@ -744,7 +879,9 @@ float svgtiny_parse_length(const char *s, int viewport_size,
 void svgtiny_parse_paint_attributes(const xmlNode *node,
                struct svgtiny_parse_state *state)
 {
-       for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
+       const xmlAttr *attr;
+
+       for (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)
@@ -779,8 +916,10 @@ void svgtiny_parse_paint_attributes(const xmlNode *node,
                                s += 13;
                                while (*s == ' ')
                                        s++;
-                               state->stroke_width = svgtiny_parse_length(s,
+                               value = strndup(s, strcspn(s, "; "));
+                               state->stroke_width = svgtiny_parse_length(value,
                                                state->viewport_width, *state);
+                               free(value);
                        }
                }
        }
@@ -858,9 +997,11 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c,
 void svgtiny_parse_font_attributes(const xmlNode *node,
                struct svgtiny_parse_state *state)
 {
+       const xmlAttr *attr;
+
        UNUSED(state);
 
-       for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
+       for (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,
@@ -907,8 +1048,9 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
        float za, zb, zc, zd, ze, zf;
        float angle, x, y;
        int n;
+       unsigned int i;
 
-       for (unsigned int i = 0; s[i]; i++)
+       for (i = 0; s[i]; i++)
                if (s[i] == ',')
                        s[i] = ' ';
 
@@ -981,12 +1123,14 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
 svgtiny_code svgtiny_add_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state)
 {
+       struct svgtiny_shape *shape;
+
        if (state->fill == svgtiny_LINEAR_GRADIENT)
                return svgtiny_add_path_linear_gradient(p, n, state);
 
        svgtiny_transform_path(p, n, state);
 
-       struct svgtiny_shape *shape = svgtiny_add_shape(state);
+       shape = svgtiny_add_shape(state);
        if (!shape) {
                free(p);
                return svgtiny_OUT_OF_MEMORY;
@@ -1018,8 +1162,10 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
        shape->text = 0;
        shape->fill = state->fill;
        shape->stroke = state->stroke;
-       shape->stroke_width = state->stroke_width *
-                       (state->ctm.a + state->ctm.d) / 2;
+       shape->stroke_width = lroundf((float) state->stroke_width *
+                       (state->ctm.a + state->ctm.d) / 2.0);
+       if (0 < state->stroke_width && shape->stroke_width == 0)
+               shape->stroke_width = 1;
 
        return shape;
 }
@@ -1032,8 +1178,11 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
 void svgtiny_transform_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state)
 {
-       for (unsigned int j = 0; j != n; ) {
+       unsigned int j;
+
+       for (j = 0; j != n; ) {
                unsigned int points = 0;
+               unsigned int k;
                switch ((int) p[j]) {
                case svgtiny_PATH_MOVE:
                case svgtiny_PATH_LINE:
@@ -1049,7 +1198,7 @@ void svgtiny_transform_path(float *p, unsigned int n,
                        assert(0);
                }
                j++;
-               for (unsigned int k = 0; k != points; k++) {
+               for (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;
@@ -1069,9 +1218,10 @@ void svgtiny_transform_path(float *p, unsigned int n,
 
 void svgtiny_free(struct svgtiny_diagram *svg)
 {
+       unsigned int i;
        assert(svg);
 
-       for (unsigned int i = 0; i != svg->shape_count; i++) {
+       for (i = 0; i != svg->shape_count; i++) {
                free(svg->shape[i].path);
                free(svg->shape[i].text);
        }
@@ -1081,3 +1231,23 @@ void svgtiny_free(struct svgtiny_diagram *svg)
        free(svg);
 }
 
+#ifndef HAVE_STRNDUP
+char *svgtiny_strndup(const char *s, size_t n)
+{
+       size_t len;
+       char *s2;
+
+       for (len = 0; len != n && s[len]; len++)
+               continue;
+
+       s2 = malloc(len + 1);
+       if (s2 == NULL)
+               return NULL;
+
+       memcpy(s2, s, len);
+       s2[len] = '\0';
+
+       return s2;
+}
+#endif
+