X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2Fsvgtiny.c;h=63c7cc2bf70d26bc9e21f16e17ab2fb9a58a5160;hb=ccc89083064d413c6785e1b740cf8e23c2eb4cfa;hp=be20b20abf1201d356291e313297bdf0bf747592;hpb=89b877c4ae7ded723b2eda2a35358dc4b2f55a76;p=libsvgtiny.git diff --git a/src/svgtiny.c b/src/svgtiny.c index be20b20..63c7cc2 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -3,9 +3,9 @@ * Licensed under the MIT License, * http://opensource.org/licenses/mit-license.php * Copyright 2008-2009 James Bursa + * Copyright 2012 Daniel Silverstone */ -#define _GNU_SOURCE /* for strndup */ #include #include #include @@ -13,8 +13,10 @@ #include #include #include -#include -#include + +#include +#include + #include "svgtiny.h" #include "svgtiny_internal.h" @@ -24,30 +26,30 @@ #define KAPPA 0.5522847498 -static svgtiny_code svgtiny_parse_svg(xmlNode *svg, +static svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_path(xmlNode *path, +static svgtiny_code svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_rect(xmlNode *rect, +static svgtiny_code svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_circle(xmlNode *circle, +static svgtiny_code svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, +static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_line(xmlNode *line, +static svgtiny_code svgtiny_parse_line(dom_element *line, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_poly(xmlNode *poly, +static svgtiny_code svgtiny_parse_poly(dom_element *poly, struct svgtiny_parse_state state, bool polygon); -static svgtiny_code svgtiny_parse_text(xmlNode *text, +static svgtiny_code svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state); -static void svgtiny_parse_position_attributes(const xmlNode *node, +static void svgtiny_parse_position_attributes(const dom_element *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height); -static void svgtiny_parse_paint_attributes(const xmlNode *node, +static void svgtiny_parse_paint_attributes(const dom_element *node, struct svgtiny_parse_state *state); -static void svgtiny_parse_font_attributes(const xmlNode *node, +static void svgtiny_parse_font_attributes(const dom_element *node, struct svgtiny_parse_state *state); -static void svgtiny_parse_transform_attributes(xmlNode *node, +static void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state); static svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state); @@ -61,18 +63,21 @@ struct svgtiny_diagram *svgtiny_create(void) { struct svgtiny_diagram *diagram; - diagram = malloc(sizeof *diagram); + diagram = calloc(sizeof(*diagram), 1); if (!diagram) return 0; - diagram->shape = 0; - diagram->shape_count = 0; - diagram->error_line = 0; - diagram->error_message = 0; - return diagram; + free(diagram); + return NULL; } +static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...) +{ + UNUSED(severity); + UNUSED(ctx); + UNUSED(msg); +} /** * Parse a block of memory into a svgtiny_diagram. @@ -82,8 +87,13 @@ 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; + dom_document *document; + dom_exception exc; + dom_xml_parser *parser; + dom_xml_error err; + dom_element *svg; + dom_string *svg_name; + lwc_string *svg_name_lwc; struct svgtiny_parse_state state; float x, y, width, height; svgtiny_code code; @@ -92,26 +102,79 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, assert(buffer); assert(url); - /* parse XML to tree */ - document = xmlReadMemory(buffer, size, url, 0, - XML_PARSE_NONET | XML_PARSE_COMPACT); - if (!document) - return svgtiny_LIBXML_ERROR; + UNUSED(url); + + parser = dom_xml_parser_create(NULL, NULL, + ignore_msg, NULL, &document); - /*xmlDebugDumpDocument(stderr, document);*/ + if (parser == NULL) + return svgtiny_LIBDOM_ERROR; + + err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size); + if (err != DOM_XML_OK) { + dom_node_unref(document); + dom_xml_parser_destroy(parser); + return svgtiny_LIBDOM_ERROR; + } + + err = dom_xml_parser_completed(parser); + if (err != DOM_XML_OK) { + dom_node_unref(document); + dom_xml_parser_destroy(parser); + return svgtiny_LIBDOM_ERROR; + } + + /* We're done parsing, drop the parser. + * We now own the document entirely. + */ + dom_xml_parser_destroy(parser); /* find root element */ - svg = xmlDocGetRootElement(document); - if (!svg) - return svgtiny_NOT_SVG; - if (strcmp((const char *) svg->name, "svg") != 0) + exc = dom_document_get_document_element(document, &svg); + if (exc != DOM_NO_ERR) { + dom_node_unref(document); + return svgtiny_LIBDOM_ERROR; + } + exc = dom_node_get_node_name(svg, &svg_name); + if (exc != DOM_NO_ERR) { + dom_node_unref(svg); + dom_node_unref(document); + return svgtiny_LIBDOM_ERROR; + } + if (lwc_intern_string("svg", 3 /* SLEN("svg") */, + &svg_name_lwc) != lwc_error_ok) { + dom_string_unref(svg_name); + dom_node_unref(svg); + dom_node_unref(document); + return svgtiny_LIBDOM_ERROR; + } + if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) { + lwc_string_unref(svg_name_lwc); + dom_string_unref(svg_name); + dom_node_unref(svg); + dom_node_unref(document); return svgtiny_NOT_SVG; + } + + lwc_string_unref(svg_name_lwc); + dom_string_unref(svg_name); /* get graphic dimensions */ state.diagram = diagram; state.document = document; state.viewport_width = viewport_width; state.viewport_height = viewport_height; + +#define SVGTINY_STRING_ACTION(s) \ + if (dom_string_create_interned((const uint8_t *) #s, \ + strlen(#s), &state.interned_##s) \ + != DOM_NO_ERR) { \ + code = svgtiny_LIBDOM_ERROR; \ + goto cleanup; \ + } +#include "svgtiny_strings.h" +#undef SVGTINY_STRING_ACTION + svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); diagram->width = width; diagram->height = height; @@ -135,9 +198,15 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, /* parse tree */ code = svgtiny_parse_svg(svg, state); - /* free XML tree */ - xmlFreeDoc(document); + dom_node_unref(svg); + dom_node_unref(document); +cleanup: +#define SVGTINY_STRING_ACTION(s) \ + if (state.interned_##s != NULL) \ + dom_string_unref(state.interned_##s); +//#include "svgtiny_strings.h" +#undef SVGTINY_STRING_ACTION return code; } @@ -146,19 +215,27 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, * Parse a or element node. */ -svgtiny_code svgtiny_parse_svg(xmlNode *svg, +svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state) { float x, y, width, height; + dom_string *view_box; + dom_element *child; + dom_exception exc; 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"); + exc = dom_element_get_attribute(svg, state.interned_viewBox, + &view_box); + if (exc != DOM_NO_ERR) { + return svgtiny_LIBDOM_ERROR; + } + if (view_box) { - const char *s = (const char *) view_box->children->content; + char *s = strndup(dom_string_data(view_box), + dom_string_length(view_box)); float min_x, min_y, vwidth, vheight; if (sscanf(s, "%f,%f,%f,%f", &min_x, &min_y, &vwidth, &vheight) == 4 || @@ -169,41 +246,78 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg, state.ctm.e += -min_x * state.ctm.a; state.ctm.f += -min_y * state.ctm.d; } + free(s); + dom_string_unref(view_box); } svgtiny_parse_transform_attributes(svg, &state); - for (xmlNode *child = svg->children; child; child = child->next) { + exc = dom_node_get_first_child(svg, &child); + if (exc != DOM_NO_ERR) { + return svgtiny_LIBDOM_ERROR; + } + while (child != NULL) { + dom_element *next; + dom_node_type nodetype; svgtiny_code code = svgtiny_OK; - if (child->type == XML_ELEMENT_NODE) { - const char *name = (const char *) child->name; - if (strcmp(name, "svg") == 0) + exc = dom_node_get_node_type(child, &nodetype); + if (exc != DOM_NO_ERR) { + dom_node_unref(child); + return svgtiny_LIBDOM_ERROR; + } + if (nodetype == DOM_ELEMENT_NODE) { + dom_string *nodename; + exc = dom_node_get_node_name(child, &nodename); + if (exc != DOM_NO_ERR) { + dom_node_unref(child); + return svgtiny_LIBDOM_ERROR; + } + if (dom_string_caseless_isequal(state.interned_svg, + nodename)) code = svgtiny_parse_svg(child, state); - else if (strcmp(name, "g") == 0) + else if (dom_string_caseless_isequal(state.interned_g, + nodename)) code = svgtiny_parse_svg(child, state); - else if (strcmp(name, "a") == 0) + else if (dom_string_caseless_isequal(state.interned_a, + nodename)) code = svgtiny_parse_svg(child, state); - else if (strcmp(name, "path") == 0) + else if (dom_string_caseless_isequal(state.interned_path, + nodename)) code = svgtiny_parse_path(child, state); - else if (strcmp(name, "rect") == 0) + else if (dom_string_caseless_isequal(state.interned_rect, + nodename)) code = svgtiny_parse_rect(child, state); - else if (strcmp(name, "circle") == 0) + else if (dom_string_caseless_isequal(state.interned_circle, + nodename)) code = svgtiny_parse_circle(child, state); - else if (strcmp(name, "ellipse") == 0) + else if (dom_string_caseless_isequal(state.interned_ellipse, + nodename)) code = svgtiny_parse_ellipse(child, state); - else if (strcmp(name, "line") == 0) + else if (dom_string_caseless_isequal(state.interned_line, + nodename)) code = svgtiny_parse_line(child, state); - else if (strcmp(name, "polyline") == 0) + else if (dom_string_caseless_isequal(state.interned_polyline, + nodename)) code = svgtiny_parse_poly(child, state, false); - else if (strcmp(name, "polygon") == 0) + else if (dom_string_caseless_isequal(state.interned_polygon, + nodename)) code = svgtiny_parse_poly(child, state, true); - else if (strcmp(name, "text") == 0) + else if (dom_string_caseless_isequal(state.interned_text, + nodename)) code = svgtiny_parse_text(child, state); + dom_string_unref(nodename); } - - if (code != svgtiny_OK) + if (code != svgtiny_OK) { + dom_node_unref(child); return code; + } + exc = dom_node_get_next_sibling(child, &next); + dom_node_unref(child); + if (exc != DOM_NO_ERR) { + return svgtiny_LIBDOM_ERROR; + } + child = next; } return svgtiny_OK; @@ -217,10 +331,15 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg, * http://www.w3.org/TR/SVG11/paths#PathElement */ -svgtiny_code svgtiny_parse_path(xmlNode *path, +svgtiny_code svgtiny_parse_path(dom_element *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); @@ -234,18 +353,15 @@ 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; @@ -447,17 +563,18 @@ svgtiny_code svgtiny_parse_path(xmlNode *path, * http://www.w3.org/TR/SVG11/shapes#RectElement */ -svgtiny_code svgtiny_parse_rect(xmlNode *rect, +svgtiny_code svgtiny_parse_rect(dom_element *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; @@ -483,12 +600,14 @@ svgtiny_code svgtiny_parse_rect(xmlNode *rect, * Parse a element node. */ -svgtiny_code svgtiny_parse_circle(xmlNode *circle, +svgtiny_code svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state) { 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) @@ -512,7 +631,7 @@ svgtiny_code svgtiny_parse_circle(xmlNode *circle, if (r == 0) return svgtiny_OK; - float *p = malloc(32 * sizeof p[0]); + p = malloc(32 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; @@ -557,12 +676,14 @@ svgtiny_code svgtiny_parse_circle(xmlNode *circle, * Parse an element node. */ -svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, +svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, struct svgtiny_parse_state state) { float x = 0, y = 0, rx = -1, ry = -1; + float *p; + xmlAttr *attr; - for (xmlAttr *attr = ellipse->properties; attr; attr = attr->next) { + 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) @@ -590,7 +711,7 @@ svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, if (rx == 0 || ry == 0) return svgtiny_OK; - float *p = malloc(32 * sizeof p[0]); + p = malloc(32 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; @@ -635,12 +756,14 @@ svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, * Parse a element node. */ -svgtiny_code svgtiny_parse_line(xmlNode *line, +svgtiny_code svgtiny_parse_line(dom_element *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) @@ -659,7 +782,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; @@ -682,10 +805,12 @@ svgtiny_code svgtiny_parse_line(xmlNode *line, * http://www.w3.org/TR/SVG11/shapes#PolygonElement */ -svgtiny_code svgtiny_parse_poly(xmlNode *poly, +svgtiny_code svgtiny_parse_poly(dom_element *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); @@ -700,17 +825,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; @@ -740,25 +865,27 @@ svgtiny_code svgtiny_parse_poly(xmlNode *poly, * Parse a or element node. */ -svgtiny_code svgtiny_parse_text(xmlNode *text, +svgtiny_code svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state) { float x, y, width, height; + float px, py; + dom_element *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) { @@ -788,16 +915,18 @@ svgtiny_code svgtiny_parse_text(xmlNode *text, * Parse x, y, width, and height attributes, if present. */ -void svgtiny_parse_position_attributes(const xmlNode *node, +void svgtiny_parse_position_attributes(const dom_element *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) @@ -860,10 +989,12 @@ float svgtiny_parse_length(const char *s, int viewport_size, * Parse paint attributes, if present. */ -void svgtiny_parse_paint_attributes(const xmlNode *node, +void svgtiny_parse_paint_attributes(const dom_element *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) @@ -898,8 +1029,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); } } } @@ -974,12 +1107,14 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c, * Parse font attributes, if present. */ -void svgtiny_parse_font_attributes(const xmlNode *node, +void svgtiny_parse_font_attributes(const dom_element *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, @@ -999,7 +1134,7 @@ void svgtiny_parse_font_attributes(const xmlNode *node, * http://www.w3.org/TR/SVG11/coords#TransformAttribute */ -void svgtiny_parse_transform_attributes(xmlNode *node, +void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state) { char *transform; @@ -1026,8 +1161,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] = ' '; @@ -1100,12 +1236,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; @@ -1153,8 +1291,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: @@ -1170,7 +1311,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; @@ -1190,9 +1331,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); } @@ -1202,3 +1344,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 +