/* * * 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 #include #include #include #include #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(""); } 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("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;iicon_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