diff options
Diffstat (limited to 'src/onecanvas.c')
-rw-r--r-- | src/onecanvas.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/onecanvas.c b/src/onecanvas.c new file mode 100644 index 0000000..e53ece7 --- /dev/null +++ b/src/onecanvas.c @@ -0,0 +1,446 @@ +/* + * + * Copyright (c) 2010 Erich Hoover + * + * libr "one canvas" - Handle multiple icons stored in a single "one canvas" + * SVG document. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * To provide feedback, report bugs, or otherwise contact me: + * ehoover at mines dot edu + * + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <stdlib.h> +#include <time.h> + +#define FALSE 0 +#define TRUE 1 + +typedef struct { + double x; + double y; + double width; + double height; + int icon_width; + int icon_height; +} IconSVG; + +typedef enum { + STATUS_FINDSVG, + STATUS_FINDMETADATA, + STATUS_FINDPUBLISHER_START, + STATUS_FINDPUBLISHER_STOP, + STATUS_FINDHIDDEN, + STATUS_FINDBOUNDS, + STATUS_FAILED, + STATUS_DONE, +} eStatus; + +typedef struct { + IconSVG **iconlist; + int iconlist_num; + eStatus status; + + char *hidden_stop; + char *hidden_start; + char *publisher_stop; + char *publisher_start; + char *coordinate_stop; + char *coordinate_start; +} OneCanvasIconInfo; + +/* + * Find the start of the next XML tag (search for '<') + */ +static inline char *xml_nextTag(char *c) +{ + c++; + if(c == NULL) + return NULL; + return strchr(c, '<'); +} + +/* + * Pull out the name/type of a tag. + */ +static inline char *xml_getTagName(char *c) +{ + char *tag_end = NULL, *tag_space, *tag_close, *tag_feed, *tag_line; + static char tagname[20]; + int tag_len; + + if(++c == NULL) + return NULL; + tag_space = strchr(c, ' '); + tag_close = strchr(c, '>'); + tag_feed = strchr(c, '\r'); + tag_line = strchr(c, '\n'); + if(tag_space) + tag_end = tag_space; + if(tag_close && tag_end > tag_close) + tag_end = tag_close; + if(tag_feed && tag_end > tag_feed) + tag_end = tag_feed; + if(tag_line && tag_end > tag_line) + tag_end = tag_line; + if(!tag_end) + return NULL; + tag_len = tag_end - c; + tag_len = tag_len > 19 ? 19 : tag_len; + strncpy(tagname, c, tag_len); + tagname[tag_len] = '\0'; + return tagname; +} + +/* + * Find the position in the string corresponding to a particular named attribute. + */ +static inline char *xml_getTagAttributePtr(char *c, char *attrname) +{ + char *end, *name; + int found; + + if(++c == NULL) + return NULL; + end = strchr(c, '>'); + while(c < end) + { + int name_len; + char *equal; + + equal = c = strchr(c, '='); + if(c == NULL) + break; + c++; + name = equal; + while(name[0] != ' ' && name[0] != '\t' && name[0] != '\n') + name--; + name++; /* don't include the space */ + name_len = equal-name; + if(name_len != strlen(attrname)) + continue; + if(strncasecmp(attrname, name, name_len) == 0) + { + found = TRUE; + break; + } + } + if(!found) + return NULL; + return c-strlen(attrname)-1; +} + +/* + * Return the value of an XML tag's named attribute. + */ +static inline char *xml_getTagAttribute(char *c, char *attrname) +{ + char *data_end; + int data_len; + char *attr; + + c = xml_getTagAttributePtr(c, attrname); + if(c == NULL) + return NULL; + c+=strlen(attrname); /* skip the name */ + c+=2; /* skip the equals sign and the quote */ + data_end = strchr(c, '"'); + data_len = data_end - c; + attr = (char *) malloc(data_len+1); + strncpy(attr, c, data_len); + attr[data_len] = '\0'; + return attr; +} + +/* + * Find the value of an XML tag attribute and convert it to a number. + */ +static inline double xml_getTagAttributeFloat(char *c, char *attrname) +{ + char *value = xml_getTagAttribute(c, attrname); + double ret; + + if(!value) + return nan("nan"); + sscanf(value, "%lf", &ret); + free(value); + return ret; +} + +/* + * Match the beginning an XML tag by "id" (preferred) or Inkscape's + * label (undesireable but acceptable). + */ +static inline char *xml_idMatchStart(char *stream_pos, char *layer_name) +{ + char *id_acceptable = xml_getTagAttribute(stream_pos, "inkscape:label"); + char *id_preferred = xml_getTagAttribute(stream_pos, "id"); + + if(id_preferred && strncasecmp(id_preferred, layer_name, strlen(layer_name)) == 0) + { + free(id_acceptable); + return id_preferred; + } + if(id_acceptable && strncasecmp(id_acceptable, layer_name, strlen(layer_name)) == 0) + { + free(id_preferred); + return id_acceptable; + } + free(id_acceptable); + free(id_preferred); + return NULL; +} + +/* + * Match the entirety of an XML tag by "id" (preferred) or Inkscape's + * label (undesireable but acceptable). + */ +static inline int xml_idMatch(char *stream_pos, char *layer_name) +{ + char *id_acceptable = xml_getTagAttribute(stream_pos, "inkscape:label"); + char *id_preferred = xml_getTagAttribute(stream_pos, "id"); + int ret = FALSE; + + if((id_preferred && strcasecmp(id_preferred, layer_name) == 0) + || (id_acceptable && strcasecmp(id_acceptable, layer_name) == 0)) + ret = TRUE; + free(id_acceptable); + free(id_preferred); + return ret; +} + +/* + * Strip all the XML tags from a string and return only the data not + * contained within any tags. + */ +static inline char *xml_stripTags(char *data, int len) +{ + char *ret = (char *) malloc(len+1); + char *tag_left, *tag_right; + + memcpy(ret, data, len+1); + ret[len] = '\0'; + while((tag_left = strchr(ret, '<')) != NULL) + { + tag_right = strchr(ret, '>'); + memmove(tag_left, tag_right+1, strlen(ret)-(tag_right-ret)); + } + return ret; +} + +/* + * Return the information for all of the icons within a "one-canvas" + * data stream. + */ +OneCanvasIconInfo onecanvas_geticons(char *stream) +{ + eStatus status = STATUS_FINDSVG; + unsigned int stream_size = 0; + OneCanvasIconInfo info; + char *publisher = NULL; + char *stream_pos; + int i; + + memset(&info, 0, sizeof(info)); + stream_pos = stream; + while(stream_pos) + { + char *name = xml_getTagName(stream_pos); + + if(!name) + { + stream_pos = xml_nextTag(stream_pos); + continue; + } + switch(status) + { + case STATUS_FINDSVG: + { + if(strcasecmp(name, "svg") == 0) + { + info.coordinate_start = xml_getTagAttributePtr(stream_pos, "x"); + info.coordinate_stop = xml_getTagAttributePtr(stream_pos, "viewBox"); + if(info.coordinate_start == NULL || info.coordinate_stop == NULL) + { + status = STATUS_FAILED; + break; + } + info.coordinate_stop = strchr(info.coordinate_stop, '"')+1; + info.coordinate_stop = strchr(info.coordinate_stop, '"')+1; + status = STATUS_FINDMETADATA; + } + } break; + case STATUS_FINDMETADATA: + { + if(strcasecmp(name, "metadata") == 0) + { + status = STATUS_FINDPUBLISHER_START; + } + else if(strcasecmp(name, "/svg") == 0) + { + status = STATUS_FAILED; + } + } break; + case STATUS_FINDPUBLISHER_START: + { + if(strcasecmp(name, "dc:publisher") == 0) + { + status = STATUS_FINDPUBLISHER_STOP; + info.publisher_start = stream_pos + strlen("<dc:publisher>"); + } + else if(strcasecmp(name, "/metadata") == 0) + { + status = STATUS_FAILED; + } + } break; + case STATUS_FINDPUBLISHER_STOP: + { + if(strcasecmp(name, "/dc:publisher") == 0) + { + info.publisher_stop = stream_pos; + publisher = xml_stripTags(info.publisher_start, info.publisher_stop-info.publisher_start); + if(strcasecmp(publisher, "one-canvas") == 0) + status = STATUS_FINDHIDDEN; + else + status = STATUS_FAILED; + } + else if(strcasecmp(name, "/metadata") == 0) + { + status = STATUS_FAILED; + } + } break; + case STATUS_FINDHIDDEN: + { + if(strcasecmp(name, "g") == 0) + { + if(xml_idMatch(stream_pos, "hidden")) + { + char *style_start; + + info.hidden_start = stream_pos; + info.hidden_stop = info.hidden_start; + style_start = xml_getTagAttributePtr(stream_pos, "style"); + if(style_start) + { + info.hidden_start = style_start; + info.hidden_stop = strchr(style_start, '"')+1; + info.hidden_stop = strchr(info.hidden_stop, '"')+1; + } + else + { + info.hidden_start += strlen("<g "); + info.hidden_stop += strlen("<g "); + } + status = STATUS_FINDBOUNDS; + } + } + } break; + case STATUS_FINDBOUNDS: + { + if(strcasecmp(name, "rect") == 0) + { + char *layer_name = xml_idMatchStart(stream_pos, "iconlayer-"); + + if(layer_name != NULL) + { + IconSVG *icon = (IconSVG *) malloc(sizeof(IconSVG)); + + icon->x = xml_getTagAttributeFloat(stream_pos, "x"); + icon->y = xml_getTagAttributeFloat(stream_pos, "y"); + icon->width = xml_getTagAttributeFloat(stream_pos, "width"); + icon->height = xml_getTagAttributeFloat(stream_pos, "height"); + sscanf(layer_name, "iconlayer-%dx%d", &(icon->icon_width), &(icon->icon_height)); + free(layer_name); + status = STATUS_FINDBOUNDS; + info.iconlist = (IconSVG **) realloc(info.iconlist, (info.iconlist_num+1)*sizeof(IconSVG *)); + info.iconlist[info.iconlist_num] = icon; + info.iconlist_num++; + } + } + else if(strcasecmp(name, "/g") == 0) + { + status = STATUS_DONE; + } + } break; + default: + break; + } + if(status == STATUS_DONE || status == STATUS_FAILED) + break; + stream_pos = xml_nextTag(stream_pos); + } + free(publisher); + info.status = status; + return info; +} + +/* + * Obtain a single icon from the "one-canvas" stream corresponding + * to a particular icon size. + */ +char *onecanvas_geticon_bysize(char *icon_data, int requested_size) +{ + OneCanvasIconInfo info = onecanvas_geticons(icon_data); + char *ret = NULL; + int i; + + if(info.status == STATUS_DONE && info.iconlist_num > 0) + { + int closest_diff = abs(info.iconlist[0]->icon_width - requested_size); + int tocoord_length, topubl_length, tohidden_length; + int icon_id = 0; + IconSVG *icon; + int ret_max; + + for(i=0;i<info.iconlist_num;i++) + { + int size_diff = abs(info.iconlist[i]->icon_width - requested_size); + + if(size_diff < closest_diff) + { + closest_diff = size_diff; + icon_id = i; + } + } + icon = info.iconlist[icon_id]; + /* Note: 200 characters is a very generous over estimate for the data we add in */ + ret_max = strlen(icon_data)+1+200; + ret = (char *) malloc(ret_max); + tocoord_length = info.coordinate_start-icon_data; + snprintf(ret, ret_max, "%.*s", tocoord_length, icon_data); + /* Output the coordinates of the icon */ + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "\nx=\"0px\"\ny=\"0px\"\n"); + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "width=\"%d\"\n", icon->icon_width); + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "height=\"%d\"\n", icon->icon_height); + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "viewBox=\"%lf %lf %lf %lf\"\n", icon->x, icon->y, icon->width, icon->height); + topubl_length = info.publisher_start-info.coordinate_stop; + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "%.*s", topubl_length, info.coordinate_stop); + /* Hide the "hidden" layer */ + tohidden_length = info.hidden_start-info.publisher_stop; + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "%.*s", tohidden_length, info.publisher_stop); + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "\ndisplay=\"none\"\n"); + /* Output the rest of the document */ + snprintf(&ret[strlen(ret)], ret_max-strlen(ret), "%s", info.hidden_stop); + } + for(i=0;i<info.iconlist_num;i++) + free(info.iconlist[i]); + free(info.iconlist); + return ret; +}
\ No newline at end of file |