/* Dia -- an diagram creation/manipulation program * Copyright (C) 1998, 1999 Alexander Larsson * * Custom Objects -- objects defined in XML rather than C. * Copyright (C) 1999 James Henstridge. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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. */ #include #include #include #include #include #include #include #include "dia_xml_libxml.h" #include "shape_info.h" #include "custom_util.h" #include "custom_object.h" #include "dia_image.h" #include "message.h" #include "intl.h" #define FONT_HEIGHT_DEFAULT 1 #define TEXT_ALIGNMENT_DEFAULT ALIGN_CENTER static ShapeInfo *load_shape_info(const gchar *filename); static GHashTable *name_to_info = NULL; ShapeInfo * shape_info_load(const gchar *filename) { ShapeInfo *info = load_shape_info(filename); if (!info) return NULL; if (!name_to_info) name_to_info = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(name_to_info, info->name, info); g_assert(shape_info_getbyname(info->name)==info); return info; } ShapeInfo * shape_info_get(ObjectNode obj_node) { ShapeInfo *info = NULL; char *str; str = xmlGetProp(obj_node, "type"); if (str && name_to_info) { info = g_hash_table_lookup(name_to_info, str); xmlFree(str); } return info; } ShapeInfo *shape_info_getbyname(const gchar *name) { if (name && name_to_info) { return g_hash_table_lookup(name_to_info,name); } return NULL; } /** * shape_info_realise : * @info : the shape to realise * * Puts the ShapeInfo into a form suitable for actual use (lazy loading) * */ void shape_info_realise(ShapeInfo* info) { GList* tmp; for (tmp = info->display_list; tmp != NULL; tmp = tmp->next) { GraphicElement *el = tmp->data; if (el->type == GE_TEXT) { /* set default values for text style */ if (!el->text.s.font_height) el->text.s.font_height = FONT_HEIGHT_DEFAULT; if (!el->text.s.font) el->text.s.font = dia_font_new_from_style(DIA_FONT_SANS,1.0); if (el->text.s.alignment == -1) el->text.s.alignment = TEXT_ALIGNMENT_DEFAULT; if (!el->text.object) { el->text.object = new_text(el->text.string, el->text.s.font, el->text.s.font_height, &el->text.anchor, &color_black, el->text.s.alignment); } text_calc_boundingbox(el->text.object, &el->text.text_bounds); } } } static void parse_path(ShapeInfo *info, const char *path_str, DiaSvgStyle *s, const char* filename) { GArray *points; gchar *pathdata = (gchar *)path_str, *unparsed; gboolean closed = FALSE; do { points = dia_svg_parse_path (pathdata, &unparsed, &closed); if (points->len > 0) { if (g_array_index(points, BezPoint, 0).type != BEZ_MOVE_TO) { message_warning (_("The file '%s' has invalid path data.\n" "svg:path data must start with moveto."), dia_message_filename(filename)); } else if (closed) { /* if there is some unclosed commands, add them as a GE_SHAPE */ GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) + points->len * sizeof(BezPoint)); el->type = GE_SHAPE; dia_svg_style_init (&el->s, s); el->npoints = points->len; memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint)); info->display_list = g_list_append(info->display_list, el); } else { /* if there is some unclosed commands, add them as a GE_PATH */ GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) + points->len * sizeof(BezPoint)); el->type = GE_PATH; dia_svg_style_init (&el->s, s); el->npoints = points->len; memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint)); info->display_list = g_list_append(info->display_list, el); } g_array_set_size (points, 0); } pathdata = unparsed; unparsed = NULL; } while (pathdata); g_array_free (points, TRUE); } static void parse_svg_node(ShapeInfo *info, xmlNodePtr node, xmlNsPtr svg_ns, DiaSvgStyle *style, const gchar *filename) { xmlChar *str; /* walk SVG node ... */ for (node = node->xmlChildrenNode; node != NULL; node = node->next) { GraphicElement *el = NULL; DiaSvgStyle s; if (xmlIsBlankNode(node)) continue; if (node->type != XML_ELEMENT_NODE || node->ns != svg_ns) continue; dia_svg_style_init (&s, style); dia_svg_parse_style(node, &s); if (!strcmp(node->name, "line")) { GraphicElementLine *line = g_new0(GraphicElementLine, 1); el = (GraphicElement *)line; line->type = GE_LINE; str = xmlGetProp(node, "x1"); if (str) { line->p1.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "y1"); if (str) { line->p1.y = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "x2"); if (str) { line->p2.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "y2"); if (str) { line->p2.y = g_ascii_strtod(str, NULL); xmlFree(str); } } else if (!strcmp(node->name, "polyline")) { GraphicElementPoly *poly; GArray *arr = g_array_new(FALSE, FALSE, sizeof(real)); real val, *rarr; char *tmp; int i; tmp = str = xmlGetProp(node, "points"); while (tmp[0] != '\0') { /* skip junk */ while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-') tmp++; if (tmp[0] == '\0') break; val = g_ascii_strtod(tmp, &tmp); g_array_append_val(arr, val); } xmlFree(str); val = 0; if (arr->len % 2 == 1) g_array_append_val(arr, val); poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point)); el = (GraphicElement *)poly; poly->type = GE_POLYLINE; poly->npoints = arr->len / 2; rarr = (real *)arr->data; for (i = 0; i < poly->npoints; i++) { poly->points[i].x = rarr[2*i]; poly->points[i].y = rarr[2*i+1]; } g_array_free(arr, TRUE); } else if (!strcmp(node->name, "polygon")) { GraphicElementPoly *poly; GArray *arr = g_array_new(FALSE, FALSE, sizeof(real)); real val, *rarr; char *tmp; int i; tmp = str = xmlGetProp(node, "points"); while (tmp[0] != '\0') { /* skip junk */ while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-') tmp++; if (tmp[0] == '\0') break; val = g_ascii_strtod(tmp, &tmp); g_array_append_val(arr, val); } xmlFree(str); val = 0; if (arr->len % 2 == 1) g_array_append_val(arr, val); poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point)); el = (GraphicElement *)poly; poly->type = GE_POLYGON; poly->npoints = arr->len / 2; rarr = (real *)arr->data; for (i = 0; i < poly->npoints; i++) { poly->points[i].x = rarr[2*i]; poly->points[i].y = rarr[2*i+1]; } g_array_free(arr, TRUE); } else if (!strcmp(node->name, "rect")) { GraphicElementRect *rect = g_new0(GraphicElementRect, 1); el = (GraphicElement *)rect; rect->type = GE_RECT; str = xmlGetProp(node, "x"); if (str) { rect->corner1.x = g_ascii_strtod(str, NULL); xmlFree(str); } else rect->corner1.x = 0; str = xmlGetProp(node, "y"); if (str) { rect->corner1.y = g_ascii_strtod(str, NULL); xmlFree(str); } else rect->corner1.y = 0; str = xmlGetProp(node, "width"); if (str) { rect->corner2.x = rect->corner1.x + g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "height"); if (str) { rect->corner2.y = rect->corner1.y + g_ascii_strtod(str, NULL); xmlFree(str); } } else if (!strcmp(node->name, "text")) { GraphicElementText *text = g_new(GraphicElementText, 1); el = (GraphicElement *)text; text->type = GE_TEXT; text->object = NULL; str = xmlGetProp(node, "x"); if (str) { text->anchor.x = g_ascii_strtod(str, NULL); xmlFree(str); } else text->anchor.x = 0; str = xmlGetProp(node, "y"); if (str) { text->anchor.y = g_ascii_strtod(str, NULL); xmlFree(str); } else text->anchor.y = 0; str = xmlNodeGetContent(node); if (str) { text->string = g_strdup(str); xmlFree(str); } else text->string = g_strdup(""); } else if (!strcmp(node->name, "circle")) { GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1); el = (GraphicElement *)ellipse; ellipse->type = GE_ELLIPSE; str = xmlGetProp(node, "cx"); if (str) { ellipse->center.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "cy"); if (str) { ellipse->center.y = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "r"); if (str) { ellipse->width = ellipse->height = 2 * g_ascii_strtod(str, NULL); xmlFree(str); } } else if (!strcmp(node->name, "ellipse")) { GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1); el = (GraphicElement *)ellipse; ellipse->type = GE_ELLIPSE; str = xmlGetProp(node, "cx"); if (str) { ellipse->center.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "cy"); if (str) { ellipse->center.y = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "rx"); if (str) { ellipse->width = 2 * g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "ry"); if (str) { ellipse->height = 2 * g_ascii_strtod(str, NULL); xmlFree(str); } } else if (!strcmp(node->name, "path")) { str = xmlGetProp(node, "d"); if (str) { parse_path(info, str, &s, filename); xmlFree(str); } } else if (!strcmp(node->name, "image")) { GraphicElementImage *image = g_new0(GraphicElementImage, 1); el = (GraphicElement *)image; image->type = GE_IMAGE; str = xmlGetProp(node, "x"); if (str) { image->topleft.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "y"); if (str) { image->topleft.y = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "width"); if (str) { image->width = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "height"); if (str) { image->height = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "xlink:href"); if (!str) /* this doesn't look right but it appears to work w/o namespace --hb */ str = xmlGetProp(node, "href"); if (str) { gchar *imgfn = g_filename_from_uri(str, NULL, NULL); if (!imgfn) /* despite it's name it ensures an absolute filename */ imgfn = custom_get_relative_filename(filename, str); image->image = dia_image_load(imgfn); /* w/o the image we would crash later */ if (!image->image) { g_warning("failed to load image file %s", imgfn ? imgfn : "(NULL)"); image->image = dia_image_get_broken(); } g_free(imgfn); xmlFree(str); } } else if (!strcmp(node->name, "g")) { /* add elements from the group element */ parse_svg_node(info, node, svg_ns, &s, filename); } if (el) { el->any.s = s; if (el->any.s.font) el->any.s.font = g_object_ref(s.font); info->display_list = g_list_append(info->display_list, el); } if (s.font) dia_font_unref (s.font); } } static void check_point(ShapeInfo *info, Point *pt) { if (pt->x < info->shape_bounds.left) info->shape_bounds.left = pt->x; if (pt->x > info->shape_bounds.right) info->shape_bounds.right = pt->x; if (pt->y < info->shape_bounds.top) info->shape_bounds.top = pt->y; if (pt->y > info->shape_bounds.bottom) info->shape_bounds.bottom = pt->y; } static void update_bounds(ShapeInfo *info) { GList *tmp; Point pt; for (tmp = info->display_list; tmp; tmp = tmp->next) { GraphicElement *el = tmp->data; int i; switch (el->type) { case GE_LINE: check_point(info, &(el->line.p1)); check_point(info, &(el->line.p2)); break; case GE_POLYLINE: for (i = 0; i < el->polyline.npoints; i++) check_point(info, &(el->polyline.points[i])); break; case GE_POLYGON: for (i = 0; i < el->polygon.npoints; i++) check_point(info, &(el->polygon.points[i])); break; case GE_RECT: check_point(info, &(el->rect.corner1)); check_point(info, &(el->rect.corner2)); break; case GE_TEXT: check_point(info, &(el->text.anchor)); break; case GE_ELLIPSE: pt = el->ellipse.center; pt.x -= el->ellipse.width / 2.0; pt.y -= el->ellipse.height / 2.0; check_point(info, &pt); pt.x += el->ellipse.width; pt.y += el->ellipse.height; check_point(info, &pt); break; case GE_PATH: case GE_SHAPE: for (i = 0; i < el->path.npoints; i++) switch (el->path.points[i].type) { case BEZ_CURVE_TO: check_point(info, &el->path.points[i].p3); check_point(info, &el->path.points[i].p2); case BEZ_MOVE_TO: case BEZ_LINE_TO: check_point(info, &el->path.points[i].p1); } break; case GE_IMAGE: check_point(info, &(el->image.topleft)); pt.x = el->image.topleft.x + el->image.width; pt.y = el->image.topleft.y + el->image.height; check_point(info, &pt); break; } } } static ShapeInfo * load_shape_info(const gchar *filename) { xmlDocPtr doc = xmlDoParseFile(filename); xmlNsPtr shape_ns, svg_ns; xmlNodePtr node, root, ext_node = NULL; ShapeInfo *info; char *tmp; int i; if (!doc) { g_warning("parse error for %s", filename); return NULL; } /* skip (emacs) comments */ root = doc->xmlRootNode; while (root && (root->type != XML_ELEMENT_NODE)) root = root->next; if (!root) return NULL; if (xmlIsBlankNode(root)) return NULL; if (!(shape_ns = xmlSearchNsByHref(doc, root, "http://www.daa.com.au/~james/dia-shape-ns"))) { xmlFreeDoc(doc); g_warning("could not find shape namespace"); return NULL; } if (!(svg_ns = xmlSearchNsByHref(doc, root, "http://www.w3.org/2000/svg"))) { xmlFreeDoc(doc); g_warning("could not find svg namespace"); return NULL; } if (root->ns != shape_ns || strcmp(root->name, "shape")) { g_warning("root element was %s -- expecting shape", root->name); xmlFreeDoc(doc); return NULL; } info = g_new0(ShapeInfo, 1); info->shape_bounds.top = DBL_MAX; info->shape_bounds.left = DBL_MAX; info->shape_bounds.bottom = -DBL_MAX; info->shape_bounds.right = -DBL_MAX; info->aspect_type = SHAPE_ASPECT_FREE; info->main_cp = -1; i = 0; for (node = root->xmlChildrenNode; node != NULL; node = node->next) { if (xmlIsBlankNode(node)) continue; if (node->type != XML_ELEMENT_NODE) continue; if (node->ns == shape_ns && !strcmp(node->name, "name")) { tmp = xmlNodeGetContent(node); g_free(info->name); info->name = g_strdup(tmp); xmlFree(tmp); } else if (node->ns == shape_ns && !strcmp(node->name, "icon")) { tmp = xmlNodeGetContent(node); g_free(info->icon); info->icon = custom_get_relative_filename(filename, tmp); xmlFree(tmp); } else if (node->ns == shape_ns && !strcmp(node->name, "connections")) { GArray *arr = g_array_new(FALSE, FALSE, sizeof(Point)); xmlNodePtr pt_node; for (pt_node = node->xmlChildrenNode; pt_node != NULL; pt_node = pt_node->next) { if (xmlIsBlankNode(pt_node)) continue; if (pt_node->ns == shape_ns && !strcmp(pt_node->name, "point")) { Point pt = { 0.0, 0.0 }; xmlChar *str; str = xmlGetProp(pt_node, "x"); if (str) { pt.x = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(pt_node, "y"); if (str) { pt.y = g_ascii_strtod(str, NULL); xmlFree(str); } g_array_append_val(arr, pt); str = xmlGetProp(pt_node, "main"); if (str && str[0] != '\0') { if (info->main_cp != -1) { message_warning("More than one main connection point in %s. Only the first one will be used.\n", info->name); } else { info->main_cp = i; } xmlFree(str); } } i++; } info->nconnections = arr->len; info->connections = (Point *)arr->data; g_array_free(arr, FALSE); } else if (node->ns == shape_ns && !strcmp(node->name, "textbox")) { xmlChar *str; str = xmlGetProp(node, "x1"); if (str) { info->text_bounds.left = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "y1"); if (str) { info->text_bounds.top = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "x2"); if (str) { info->text_bounds.right = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "y2"); if (str) { info->text_bounds.bottom = g_ascii_strtod(str, NULL); xmlFree(str); } info->resize_with_text = TRUE; str = xmlGetProp(node, "resize"); if (str) { info->resize_with_text = TRUE; if (!strcmp(str,"no")) info->resize_with_text = FALSE; xmlFree(str); } info->text_align = ALIGN_CENTER; str = xmlGetProp(node, "align"); if (str) { if (!strcmp(str, "left")) info->text_align = ALIGN_LEFT; else if (!strcmp(str, "right")) info->text_align = ALIGN_RIGHT; xmlFree(str); } info->has_text = TRUE; } else if (node->ns == shape_ns && !strcmp(node->name, "aspectratio")) { tmp = xmlGetProp(node, "type"); if (tmp) { if (!strcmp(tmp, "free")) info->aspect_type = SHAPE_ASPECT_FREE; else if (!strcmp(tmp, "fixed")) info->aspect_type = SHAPE_ASPECT_FIXED; else if (!strcmp(tmp, "range")) { char *str; info->aspect_type = SHAPE_ASPECT_RANGE; info->aspect_min = 0.0; info->aspect_max = G_MAXFLOAT; str = xmlGetProp(node, "min"); if (str) { info->aspect_min = g_ascii_strtod(str, NULL); xmlFree(str); } str = xmlGetProp(node, "max"); if (str) { info->aspect_max = g_ascii_strtod(str, NULL); xmlFree(str); } if (info->aspect_max < info->aspect_min) { real asp = info->aspect_max; info->aspect_max = info->aspect_min; info->aspect_min = asp; } } xmlFree(tmp); } } else if (node->ns == svg_ns && !strcmp(node->name, "svg")) { DiaSvgStyle s = { 1.0, DIA_SVG_COLOUR_FOREGROUND, DIA_SVG_COLOUR_NONE, DIA_SVG_LINECAPS_DEFAULT, DIA_SVG_LINEJOIN_DEFAULT, DIA_SVG_LINESTYLE_DEFAULT, 1.0 }; dia_svg_parse_style(node, &s); parse_svg_node(info, node, svg_ns, &s, filename); update_bounds(info); } else if (!strcmp(node->name, "ext_attributes")) { ext_node = node; } } /*MC 11/03 parse ext attributes if any & prepare prop tables */ custom_setup_properties (info, ext_node); xmlFreeDoc(doc); return info; } void shape_info_print(ShapeInfo *info) { GList *tmp; int i; g_print("Name : %s\n", info->name); g_print("Connections :\n"); for (i = 0; i < info->nconnections; i++) g_print(" (%g, %g)\n", info->connections[i].x, info->connections[i].y); g_print("Shape bounds: (%g, %g) - (%g, %g)\n", info->shape_bounds.left, info->shape_bounds.top, info->shape_bounds.right, info->shape_bounds.bottom); if (info->has_text) g_print("Text bounds : (%g, %g) - (%g, %g)\n", info->text_bounds.left, info->text_bounds.top, info->text_bounds.right, info->text_bounds.bottom); g_print("Aspect ratio: "); switch (info->aspect_type) { case SHAPE_ASPECT_FREE: g_print("free\n"); break; case SHAPE_ASPECT_FIXED: g_print("fixed\n"); break; case SHAPE_ASPECT_RANGE: g_print("range %g - %g\n", info->aspect_min, info->aspect_max); break; } g_print("Display list:\n"); for (tmp = info->display_list; tmp; tmp = tmp->next) { GraphicElement *el = tmp->data; int i; switch (el->type) { case GE_LINE: g_print(" line: (%g, %g) (%g, %g)\n", el->line.p1.x, el->line.p1.y, el->line.p2.x, el->line.p2.y); break; case GE_POLYLINE: g_print(" polyline:"); for (i = 0; i < el->polyline.npoints; i++) g_print(" (%g, %g)", el->polyline.points[i].x, el->polyline.points[i].y); g_print("\n"); break; case GE_POLYGON: g_print(" polygon:"); for (i = 0; i < el->polygon.npoints; i++) g_print(" (%g, %g)", el->polygon.points[i].x, el->polygon.points[i].y); g_print("\n"); break; case GE_RECT: g_print(" rect: (%g, %g) (%g, %g)\n", el->rect.corner1.x, el->rect.corner1.y, el->rect.corner2.x, el->rect.corner2.y); break; case GE_TEXT: g_print(" text: (%g, %g)\n", el->text.anchor.x, el->text.anchor.y); break; case GE_ELLIPSE: g_print(" ellipse: center=(%g, %g) width=%g height=%g\n", el->ellipse.center.x, el->ellipse.center.y, el->ellipse.width, el->ellipse.height); break; case GE_PATH: g_print(" path:"); for (i = 0; i < el->path.npoints; i++) switch (el->path.points[i].type) { case BEZ_MOVE_TO: g_print(" M (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y); break; case BEZ_LINE_TO: g_print(" L (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y); break; case BEZ_CURVE_TO: g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y, el->path.points[i].p2.x, el->path.points[i].p2.y, el->path.points[i].p3.x, el->path.points[i].p3.y); break; } break; case GE_SHAPE: g_print(" shape:"); for (i = 0; i < el->path.npoints; i++) switch (el->path.points[i].type) { case BEZ_MOVE_TO: g_print(" M (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y); break; case BEZ_LINE_TO: g_print(" L (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y); break; case BEZ_CURVE_TO: g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x, el->path.points[i].p1.y, el->path.points[i].p2.x, el->path.points[i].p2.y, el->path.points[i].p3.x, el->path.points[i].p3.y); break; } break; case GE_IMAGE : g_print(" image topleft=(%g, %g) width=%g height=%g file=%s\n", el->image.topleft.x, el->image.topleft.y, el->image.width, el->image.height, el->image.image ? dia_image_filename(el->image.image) : "(nil)"); break; default: break; } } g_print("\n"); }